Compare commits
189 Commits
5.6.2
...
5.0.11.RELEASE
| Author | SHA1 | Date | |
|---|---|---|---|
| 26d9decb03 | |||
| 973637b78b | |||
| 784b295627 | |||
| d6a6be8fe0 | |||
| f401739165 | |||
| 7fb4609fec | |||
| 36c98d62f7 | |||
| 3c541a1783 | |||
| 1d206052df | |||
| 88d57e0b8e | |||
| d85200682c | |||
| 99019c80a7 | |||
| aea9ee5f2a | |||
| 36adc37226 | |||
| 78a6c48f84 | |||
| f6657b7771 | |||
| d8f039b32b | |||
| ec77c630ec | |||
| 381b4c118d | |||
| 9eb58c94e0 | |||
| 59e138cb6a | |||
| cb0ff5cca3 | |||
| 0d566c02ce | |||
| 5d49ac7e6c | |||
| d5949597b7 | |||
| 6e0a5b2581 | |||
| dcc6ca0a6f | |||
| 7f5f86d9bc | |||
| cbf1c3d45a | |||
| 563a055158 | |||
| ae343ca09c | |||
| 26eb0b672b | |||
| 9be73e4db5 | |||
| 5b3fe7007e | |||
| aaa8abdfb8 | |||
| 0aedbf4496 | |||
| 0c7c6d0d49 | |||
| 5c220b9fc5 | |||
| ddad61de14 | |||
| e6a1552eb1 | |||
| 212ae6bddc | |||
| 82083d9112 | |||
| d6da4d20b4 | |||
| 0c32cbd862 | |||
| c33fc00507 | |||
| 1150dafd55 | |||
| 4f80b9d737 | |||
| e6f5d4f13b | |||
| 23dfe33877 | |||
| 0e2b75e994 | |||
| e9e57b7509 | |||
| c60a3e165e | |||
| 3a756bfa57 | |||
| 800157fe42 | |||
| ba32758b92 | |||
| 05e940692a | |||
| 5e74da9b4f | |||
| 9a49795abc | |||
| 566fb939ca | |||
| 5b26158f4d | |||
| a7eaaec431 | |||
| 5fde56d84a | |||
| 10d53be70b | |||
| 93e0ce5236 | |||
| 677a9634b8 | |||
| 6d72c3f3aa | |||
| 67fcd51d59 | |||
| 15a777d534 | |||
| 7e0622c7be | |||
| 07dd883d6d | |||
| 8170e47ff2 | |||
| c408fb7edf | |||
| 4ff0fdfff9 | |||
| 47b82ccd83 | |||
| 54cc2e4509 | |||
| 31650d7eaa | |||
| 5888a3f6b7 | |||
| 87a5c16b53 | |||
| 4b407189c2 | |||
| 96c944c4fc | |||
| 1bbae835a4 | |||
| de640a10d6 | |||
| 85c8d53b36 | |||
| 2d6bcf2cc3 | |||
| 18b98fc607 | |||
| 7bf8091e22 | |||
| d19d03dea8 | |||
| 35df3ff89b | |||
| 5f75e137d8 | |||
| b988f656c6 | |||
| 06fff9441e | |||
| edb354a2a9 | |||
| 1ce0cf44a8 | |||
| ffd8f7c8e4 | |||
| ecaa2c5b1c | |||
| a1c381c276 | |||
| 1b213d65d2 | |||
| eb897ac69c | |||
| bd8180da12 | |||
| 5dabff25c3 | |||
| 09429479fe | |||
| dc602c0840 | |||
| fedc183b5b | |||
| d3f12f0ee7 | |||
| 2a723cf5c7 | |||
| 6f32303866 | |||
| 400adaef1c | |||
| 9924d6e920 | |||
| 6cfd30aee8 | |||
| 1abdb69615 | |||
| b07f6b0178 | |||
| f0eec1b03b | |||
| 6ca6d80184 | |||
| ce5eec8761 | |||
| 86e03b3fb4 | |||
| c909264b31 | |||
| f52ab9a146 | |||
| ad2cb501b8 | |||
| 8659a4b555 | |||
| e04b29426b | |||
| 350d434e28 | |||
| bbfc3d2b4b | |||
| a7e4f36dbe | |||
| e61aafe115 | |||
| ff95388ce1 | |||
| 545228cf8d | |||
| 7b8fa90d96 | |||
| c30e218f1f | |||
| 70d284865f | |||
| a4b5523063 | |||
| 8883ec17e5 | |||
| f524da3edc | |||
| e1e464387e | |||
| a5dc48f9d9 | |||
| 9b722a9c85 | |||
| 884a4e46df | |||
| 01c2d57d46 | |||
| c9e42ce756 | |||
| 9b0b933736 | |||
| 0169b8746b | |||
| 25a87051a6 | |||
| 7edf9fc6c8 | |||
| a06f1e3936 | |||
| b296d2eaa5 | |||
| 11a8c76991 | |||
| 2f441f18e6 | |||
| a7de1e363f | |||
| 32f5fb5eb2 | |||
| f7f6798f71 | |||
| 0b72f93027 | |||
| 5b751baf61 | |||
| ec9ab1965a | |||
| 30aede82da | |||
| 2686452b99 | |||
| 7e470ff5bd | |||
| df6a6e5991 | |||
| 1500a22439 | |||
| 64cb256bb9 | |||
| e6d68acfd6 | |||
| bc2a7ac394 | |||
| 55019b2f0b | |||
| 151b545ed0 | |||
| c67ce144b9 | |||
| 6729c39905 | |||
| 197439f9e1 | |||
| 70a11267fa | |||
| 4e0c165d47 | |||
| 93844b1823 | |||
| a30b3321c4 | |||
| ffb8c1978a | |||
| 4722ab21bf | |||
| 335735e227 | |||
| 501269a51f | |||
| 6b58218256 | |||
| de1137fc5e | |||
| e68657216c | |||
| bfada59a1e | |||
| bc17860cb6 | |||
| 5e351a4f75 | |||
| 0458ca88e6 | |||
| a7a71d6cef | |||
| a7f180b2c7 | |||
| 87f266133b | |||
| 8f8deac0f4 | |||
| 9830d01acc | |||
| d8fea5c6f1 | |||
| 4d410b0b61 | |||
| 24a4fbfe56 | |||
| d5d1c30d1d |
Vendored
+5
-16
@@ -30,7 +30,11 @@ try {
|
||||
checkout scm
|
||||
withCredentials([string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')]) {
|
||||
try {
|
||||
sh "./gradlew clean sonarqube -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
|
||||
if ("master" == env.BRANCH_NAME) {
|
||||
sh "./gradlew sonarqube -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
|
||||
} else {
|
||||
sh "./gradlew sonarqube -PexcludeProjects='**/samples/**' -Dsonar.projectKey='spring-security-${env.BRANCH_NAME}' -Dsonar.projectName='spring-security-${env.BRANCH_NAME}' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
} catch(Exception e) {
|
||||
currentBuild.result = 'FAILED: sonar'
|
||||
throw e
|
||||
@@ -38,21 +42,6 @@ try {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
springio: {
|
||||
stage('Spring IO') {
|
||||
node {
|
||||
checkout scm
|
||||
try {
|
||||
sh "./gradlew clean springIoCheck -PplatformVersion=Cairo-BUILD-SNAPSHOT -PexcludeProjects='**/samples/**' --refresh-dependencies --no-daemon --stacktrace"
|
||||
} catch(Exception e) {
|
||||
currentBuild.result = 'FAILED: springio'
|
||||
throw e
|
||||
} finally {
|
||||
junit '**/build/spring-io*-results/*.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(currentBuild.result == 'SUCCESS') {
|
||||
|
||||
+3
-10
@@ -18,15 +18,8 @@ ext.milestoneBuild = !(snapshotBuild || releaseBuild)
|
||||
|
||||
dependencyManagementExport.projects = subprojects.findAll { !it.name.contains('-boot') }
|
||||
|
||||
// Disable JaCoCo when not explicitly requested to enable caching of test
|
||||
// See https://discuss.gradle.org/t/do-not-cache-if-condition-matched-jacoco-agent-configured-with-append-true-satisfied/23504
|
||||
gradle.taskGraph.whenReady { graph ->
|
||||
def enabled = graph.allTasks.any { it instanceof JacocoReport }
|
||||
subprojects { project ->
|
||||
project.plugins.withType(JacocoPlugin) {
|
||||
project.tasks.withType(Test) {
|
||||
jacoco.enabled = enabled
|
||||
}
|
||||
}
|
||||
subprojects {
|
||||
plugins.withType(JavaPlugin) {
|
||||
project.sourceCompatibility='1.8'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 versions
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class VersionsResourceTasks extends DefaultTask {
|
||||
@OutputFile
|
||||
File versionsFile;
|
||||
|
||||
@Input
|
||||
Closure<Map<String,String>> versions;
|
||||
|
||||
void setVersions(Map<String,String> versions) {
|
||||
this.versions = { versions };
|
||||
}
|
||||
|
||||
void setVersions(Closure<Map<String,String>> versions) {
|
||||
this.versions = versions
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void generateVersions() {
|
||||
versionsFile.parentFile.mkdirs()
|
||||
versionsFile.createNewFile()
|
||||
Properties versionsProperties = new Properties()
|
||||
versionsProperties.putAll(versions.call())
|
||||
versionsProperties.store(versionsFile.newWriter(), null)
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -137,7 +137,9 @@ public class CasAuthenticationTokenMixinTests {
|
||||
assertThat(token.getUserDetails()).isNotNull().isInstanceOf(User.class);
|
||||
assertThat(token.getAssertion()).isNotNull().isInstanceOf(AssertionImpl.class);
|
||||
assertThat(token.getKeyHash()).isEqualTo(KEY.hashCode());
|
||||
assertThat(token.getUserDetails().getAuthorities()).hasSize(1).contains(new SimpleGrantedAuthority("ROLE_USER"));
|
||||
assertThat(token.getUserDetails().getAuthorities())
|
||||
.extracting(GrantedAuthority::getAuthority)
|
||||
.containsOnly("ROLE_USER");
|
||||
assertThat(token.getAssertion().getAuthenticationDate()).isEqualTo(START_DATE);
|
||||
assertThat(token.getAssertion().getValidFromDate()).isEqualTo(START_DATE);
|
||||
assertThat(token.getAssertion().getValidUntilDate()).isEqualTo(END_DATE);
|
||||
|
||||
+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"));
|
||||
|
||||
@@ -36,6 +36,7 @@ dependencies {
|
||||
testCompile spockDependencies
|
||||
testCompile 'ch.qos.logback:logback-classic'
|
||||
testCompile 'javax.annotation:jsr250-api:1.0'
|
||||
testCompile 'javax.xml.bind:jaxb-api'
|
||||
testCompile 'ldapsdk:ldapsdk:4.1'
|
||||
testCompile('net.sourceforge.htmlunit:htmlunit') {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ import org.springframework.security.config.annotation.authentication.configurati
|
||||
* {@link AuthenticationConfiguration} to configure the global
|
||||
* {@link AuthenticationManagerBuilder}.
|
||||
*
|
||||
* @since 3.2.1
|
||||
* @since 5.0
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Order(100)
|
||||
|
||||
+6
@@ -29,6 +29,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
@@ -382,6 +383,11 @@ public final class WebSecurity extends
|
||||
throws BeansException {
|
||||
this.defaultWebSecurityExpressionHandler
|
||||
.setApplicationContext(applicationContext);
|
||||
try {
|
||||
this.defaultWebSecurityExpressionHandler.setPermissionEvaluator(applicationContext.getBean(
|
||||
PermissionEvaluator.class));
|
||||
} catch(NoSuchBeanDefinitionException e) {}
|
||||
|
||||
this.ignoredRequestRegistry = new IgnoredRequestConfigurer(applicationContext);
|
||||
try {
|
||||
this.httpFirewall = applicationContext.getBean(HttpFirewall.class);
|
||||
|
||||
+1
@@ -200,6 +200,7 @@ public abstract class WebSecurityConfigurerAdapter implements
|
||||
|
||||
AuthenticationManager authenticationManager = authenticationManager();
|
||||
authenticationBuilder.parentAuthenticationManager(authenticationManager);
|
||||
authenticationBuilder.authenticationEventPublisher(eventPublisher);
|
||||
Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
|
||||
|
||||
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
|
||||
|
||||
+7
@@ -23,6 +23,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||
import org.springframework.security.web.savedrequest.RequestCache;
|
||||
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
@@ -85,6 +86,12 @@ public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>> exte
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public H disable() {
|
||||
getBuilder().setSharedObject(RequestCache.class, new NullRequestCache());
|
||||
return super.disable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(H http) throws Exception {
|
||||
http.setSharedObject(RequestCache.class, getRequestCache(http));
|
||||
|
||||
+11
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* 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.
|
||||
@@ -124,6 +124,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
|
||||
private final RedirectionEndpointConfig redirectionEndpointConfig = new RedirectionEndpointConfig();
|
||||
private final UserInfoEndpointConfig userInfoEndpointConfig = new UserInfoEndpointConfig();
|
||||
private String loginPage;
|
||||
private String loginProcessingUrl = OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
|
||||
|
||||
/**
|
||||
* Sets the repository of client registrations.
|
||||
@@ -156,6 +157,13 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2LoginConfigurer<B> loginProcessingUrl(String loginProcessingUrl) {
|
||||
Assert.hasText(loginProcessingUrl, "loginProcessingUrl cannot be empty");
|
||||
this.loginProcessingUrl = loginProcessingUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link AuthorizationEndpointConfig} for configuring the Authorization Server's Authorization Endpoint.
|
||||
*
|
||||
@@ -378,9 +386,9 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
|
||||
new OAuth2LoginAuthenticationFilter(
|
||||
this.getClientRegistrationRepository(),
|
||||
this.getAuthorizedClientService(),
|
||||
OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
|
||||
this.loginProcessingUrl);
|
||||
this.setAuthenticationFilter(authenticationFilter);
|
||||
this.loginProcessingUrl(OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
|
||||
super.loginProcessingUrl(this.loginProcessingUrl);
|
||||
if (this.loginPage != null) {
|
||||
super.loginPage(this.loginPage);
|
||||
}
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ import java.lang.annotation.Target;
|
||||
* @EnableWebFluxSecurity
|
||||
* public class MyExplicitSecurityConfiguration {
|
||||
* @Bean
|
||||
* SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
* http
|
||||
* .authorizeExchange()
|
||||
* .anyExchange().authenticated()
|
||||
|
||||
+2
@@ -18,6 +18,7 @@ package org.springframework.security.config.annotation.web.reactive;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
@@ -35,6 +36,7 @@ import static org.springframework.security.config.web.server.ServerHttpSecurity.
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@Configuration
|
||||
class ServerHttpSecurityConfiguration implements WebFluxConfigurer {
|
||||
private static final String BEAN_NAME_PREFIX = "org.springframework.security.config.annotation.web.reactive.HttpSecurityConfiguration.";
|
||||
private static final String HTTPSECURITY_BEAN_NAME = BEAN_NAME_PREFIX + "httpSecurity";
|
||||
|
||||
+651
-21
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -739,7 +739,7 @@ hsts-options.attlist &=
|
||||
## Specifies if subdomains should be included. Default true.
|
||||
attribute include-subdomains {xsd:boolean}?
|
||||
hsts-options.attlist &=
|
||||
## Specifies the maximum ammount of time the host should be considered a Known HSTS Host. Default one year.
|
||||
## Specifies the maximum amount of time the host should be considered a Known HSTS Host. Default one year.
|
||||
attribute max-age-seconds {xsd:integer}?
|
||||
hsts-options.attlist &=
|
||||
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
||||
|
||||
+1
-1
@@ -2299,7 +2299,7 @@
|
||||
</xs:attribute>
|
||||
<xs:attribute name="max-age-seconds" type="xs:integer">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies the maximum ammount of time the host should be considered a Known HSTS Host.
|
||||
<xs:documentation>Specifies the maximum amount of time the host should be considered a Known HSTS Host.
|
||||
Default one year.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
|
||||
+1
-1
@@ -759,7 +759,7 @@ hsts-options.attlist &=
|
||||
## Specifies if subdomains should be included. Default true.
|
||||
attribute include-subdomains {xsd:boolean}?
|
||||
hsts-options.attlist &=
|
||||
## Specifies the maximum ammount of time the host should be considered a Known HSTS Host. Default one year.
|
||||
## Specifies the maximum amount of time the host should be considered a Known HSTS Host. Default one year.
|
||||
attribute max-age-seconds {xsd:integer}?
|
||||
hsts-options.attlist &=
|
||||
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
||||
|
||||
+1
-1
@@ -2358,7 +2358,7 @@
|
||||
</xs:attribute>
|
||||
<xs:attribute name="max-age-seconds" type="xs:integer">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies the maximum ammount of time the host should be considered a Known HSTS Host.
|
||||
<xs:documentation>Specifies the maximum amount of time the host should be considered a Known HSTS Host.
|
||||
Default one year.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
|
||||
+1
-1
@@ -768,7 +768,7 @@ hsts-options.attlist &=
|
||||
## Specifies if subdomains should be included. Default true.
|
||||
attribute include-subdomains {xsd:boolean}?
|
||||
hsts-options.attlist &=
|
||||
## Specifies the maximum ammount of time the host should be considered a Known HSTS Host. Default one year.
|
||||
## Specifies the maximum amount of time the host should be considered a Known HSTS Host. Default one year.
|
||||
attribute max-age-seconds {xsd:integer}?
|
||||
hsts-options.attlist &=
|
||||
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
||||
|
||||
+1
-1
@@ -2387,7 +2387,7 @@
|
||||
</xs:attribute>
|
||||
<xs:attribute name="max-age-seconds" type="xs:integer">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies the maximum ammount of time the host should be considered a Known HSTS Host.
|
||||
<xs:documentation>Specifies the maximum amount of time the host should be considered a Known HSTS Host.
|
||||
Default one year.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
|
||||
+2
-2
@@ -748,7 +748,7 @@ csrf-options.attlist &=
|
||||
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
|
||||
attribute request-matcher-ref { xsd:token }?
|
||||
csrf-options.attlist &=
|
||||
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
|
||||
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by LazyCsrfTokenRepository.
|
||||
attribute token-repository-ref { xsd:token }?
|
||||
|
||||
headers =
|
||||
@@ -770,7 +770,7 @@ hsts-options.attlist &=
|
||||
## Specifies if subdomains should be included. Default true.
|
||||
attribute include-subdomains {xsd:boolean}?
|
||||
hsts-options.attlist &=
|
||||
## Specifies the maximum ammount of time the host should be considered a Known HSTS Host. Default one year.
|
||||
## Specifies the maximum amount of time the host should be considered a Known HSTS Host. Default one year.
|
||||
attribute max-age-seconds {xsd:integer}?
|
||||
hsts-options.attlist &=
|
||||
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
||||
|
||||
+3
-2
@@ -2337,7 +2337,8 @@
|
||||
</xs:attribute>
|
||||
<xs:attribute name="token-repository-ref" type="xs:token">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
|
||||
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by
|
||||
LazyCsrfTokenRepository.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
@@ -2401,7 +2402,7 @@
|
||||
</xs:attribute>
|
||||
<xs:attribute name="max-age-seconds" type="xs:integer">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies the maximum ammount of time the host should be considered a Known HSTS Host.
|
||||
<xs:documentation>Specifies the maximum amount of time the host should be considered a Known HSTS Host.
|
||||
Default one year.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
|
||||
+2
-2
@@ -738,7 +738,7 @@ csrf-options.attlist &=
|
||||
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
|
||||
attribute request-matcher-ref { xsd:token }?
|
||||
csrf-options.attlist &=
|
||||
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
|
||||
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by LazyCsrfTokenRepository.
|
||||
attribute token-repository-ref { xsd:token }?
|
||||
|
||||
headers =
|
||||
@@ -760,7 +760,7 @@ hsts-options.attlist &=
|
||||
## Specifies if subdomains should be included. Default true.
|
||||
attribute include-subdomains {xsd:boolean}?
|
||||
hsts-options.attlist &=
|
||||
## Specifies the maximum ammount of time the host should be considered a Known HSTS Host. Default one year.
|
||||
## Specifies the maximum amount of time the host should be considered a Known HSTS Host. Default one year.
|
||||
attribute max-age-seconds {xsd:integer}?
|
||||
hsts-options.attlist &=
|
||||
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
||||
|
||||
+3
-2
@@ -2232,7 +2232,8 @@
|
||||
</xs:attribute>
|
||||
<xs:attribute name="token-repository-ref" type="xs:token">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
|
||||
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by
|
||||
LazyCsrfTokenRepository.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
@@ -2296,7 +2297,7 @@
|
||||
</xs:attribute>
|
||||
<xs:attribute name="max-age-seconds" type="xs:integer">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies the maximum ammount of time the host should be considered a Known HSTS Host.
|
||||
<xs:documentation>Specifies the maximum amount of time the host should be considered a Known HSTS Host.
|
||||
Default one year.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
|
||||
-344
@@ -1,344 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.config.annotation.web.configuration
|
||||
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
import static org.junit.Assert.*
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.expression.ExpressionParser
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.security.access.expression.SecurityExpressionHandler
|
||||
import org.springframework.security.authentication.AuthenticationManager
|
||||
import org.springframework.security.config.annotation.BaseSpringSpec
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurationTests.DuplicateOrderConfig;
|
||||
import org.springframework.security.web.FilterChainProxy
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator
|
||||
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator
|
||||
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher
|
||||
import org.springframework.test.util.ReflectionTestUtils
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
class WebSecurityConfigurationTests extends BaseSpringSpec {
|
||||
|
||||
def "WebSecurityConfigurers are sorted"() {
|
||||
when:
|
||||
loadConfig(SortedWebSecurityConfigurerAdaptersConfig);
|
||||
List<SecurityFilterChain> filterChains = context.getBean(FilterChainProxy).filterChains
|
||||
then:
|
||||
filterChains[0].requestMatcher.pattern == "/ignore1"
|
||||
filterChains[0].filters.empty
|
||||
filterChains[1].requestMatcher.pattern == "/ignore2"
|
||||
filterChains[1].filters.empty
|
||||
|
||||
filterChains[2].requestMatcher.pattern == "/role1/**"
|
||||
filterChains[3].requestMatcher.pattern == "/role2/**"
|
||||
filterChains[4].requestMatcher.pattern == "/role3/**"
|
||||
filterChains[5].requestMatcher.class == AnyRequestMatcher
|
||||
}
|
||||
|
||||
|
||||
@EnableWebSecurity
|
||||
static class SortedWebSecurityConfigurerAdaptersConfig {
|
||||
public AuthenticationManager authenticationManager() throws Exception {
|
||||
return new AuthenticationManagerBuilder()
|
||||
.inMemoryAuthentication()
|
||||
.withUser("marissa").password("koala").roles("USER").and()
|
||||
.withUser("paul").password("emu").roles("USER").and()
|
||||
.and()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Order(1)
|
||||
public static class WebConfigurer1 extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
web
|
||||
.ignoring()
|
||||
.antMatchers("/ignore1","/ignore2");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.antMatcher("/role1/**")
|
||||
.authorizeRequests()
|
||||
.anyRequest().hasRole("1");
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Order(2)
|
||||
public static class WebConfigurer2 extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.antMatcher("/role2/**")
|
||||
.authorizeRequests()
|
||||
.anyRequest().hasRole("2");
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Order(3)
|
||||
public static class WebConfigurer3 extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.antMatcher("/role3/**")
|
||||
.authorizeRequests()
|
||||
.anyRequest().hasRole("3");
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class WebConfigurer4 extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().hasRole("4");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "WebSecurityConfigurers fails with duplicate order"() {
|
||||
when:
|
||||
loadConfig(DuplicateOrderConfig);
|
||||
then:
|
||||
BeanCreationException e = thrown()
|
||||
e.message.contains "@Order on WebSecurityConfigurers must be unique"
|
||||
e.message.contains DuplicateOrderConfig.WebConfigurer1.class.name
|
||||
e.message.contains DuplicateOrderConfig.WebConfigurer2.class.name
|
||||
}
|
||||
|
||||
|
||||
@EnableWebSecurity
|
||||
static class DuplicateOrderConfig {
|
||||
public AuthenticationManager authenticationManager() throws Exception {
|
||||
return new AuthenticationManagerBuilder()
|
||||
.inMemoryAuthentication()
|
||||
.withUser("user").password("password").roles("USER").and()
|
||||
.and()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class WebConfigurer1 extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.antMatcher("/role1/**")
|
||||
.authorizeRequests()
|
||||
.anyRequest().hasRole("1");
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class WebConfigurer2 extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.antMatcher("/role2/**")
|
||||
.authorizeRequests()
|
||||
.anyRequest().hasRole("2");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "Override privilegeEvaluator"() {
|
||||
setup:
|
||||
WebInvocationPrivilegeEvaluator privilegeEvaluator = Mock()
|
||||
PrivilegeEvaluatorConfigurerAdapterConfig.PE = privilegeEvaluator
|
||||
when:
|
||||
loadConfig(PrivilegeEvaluatorConfigurerAdapterConfig)
|
||||
then:
|
||||
context.getBean(WebInvocationPrivilegeEvaluator) == privilegeEvaluator
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class PrivilegeEvaluatorConfigurerAdapterConfig extends WebSecurityConfigurerAdapter {
|
||||
static WebInvocationPrivilegeEvaluator PE
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
web
|
||||
.privilegeEvaluator(PE)
|
||||
}
|
||||
}
|
||||
|
||||
def "Override webSecurityExpressionHandler"() {
|
||||
setup:
|
||||
SecurityExpressionHandler expressionHandler = Mock()
|
||||
ExpressionParser parser = Mock()
|
||||
WebSecurityExpressionHandlerConfig.EH = expressionHandler
|
||||
when:
|
||||
loadConfig(WebSecurityExpressionHandlerConfig)
|
||||
then:
|
||||
context.getBean(SecurityExpressionHandler) == expressionHandler
|
||||
1 * expressionHandler.getExpressionParser() >> parser
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class WebSecurityExpressionHandlerConfig extends WebSecurityConfigurerAdapter {
|
||||
static SecurityExpressionHandler EH
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
web
|
||||
.expressionHandler(EH)
|
||||
}
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.expressionHandler(EH)
|
||||
.anyRequest().authenticated()
|
||||
}
|
||||
}
|
||||
|
||||
def "#138 webSecurityExpressionHandler defaults"() {
|
||||
when:
|
||||
loadConfig(WebSecurityExpressionHandlerDefaultsConfig)
|
||||
then:
|
||||
SecurityExpressionHandler wseh = context.getBean(SecurityExpressionHandler)
|
||||
wseh instanceof DefaultWebSecurityExpressionHandler
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class WebSecurityExpressionHandlerDefaultsConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
}
|
||||
}
|
||||
|
||||
def "#138 WebInvocationPrivilegeEvaluator defaults"() {
|
||||
when:
|
||||
loadConfig(WebInvocationPrivilegeEvaluatorDefaultsConfig)
|
||||
then:
|
||||
WebInvocationPrivilegeEvaluator wipe = context.getBean(WebInvocationPrivilegeEvaluator)
|
||||
wipe instanceof DefaultWebInvocationPrivilegeEvaluator
|
||||
wipe.securityInterceptor != null
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class WebInvocationPrivilegeEvaluatorDefaultsConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
}
|
||||
}
|
||||
|
||||
def "SEC-2303: DefaultExpressionHandler has bean resolver set"() {
|
||||
when:
|
||||
loadConfig(DefaultExpressionHandlerSetsBeanResolverConfig)
|
||||
then: "the exposed bean has a BeanResolver set"
|
||||
ReflectionTestUtils.getField(context.getBean(SecurityExpressionHandler),"br")
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(request, response, chain)
|
||||
then: "we can use the BeanResolver with a grant"
|
||||
noExceptionThrown()
|
||||
when: "we can use the Beanresolver with a deny"
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(method:'POST'), response, chain)
|
||||
then:
|
||||
noExceptionThrown()
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class DefaultExpressionHandlerSetsBeanResolverConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().access("request.method == 'GET' ? @b.grant() : @b.deny()")
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MyBean b() {
|
||||
new MyBean()
|
||||
}
|
||||
|
||||
static class MyBean {
|
||||
boolean deny() {
|
||||
false
|
||||
}
|
||||
|
||||
boolean grant() {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "SEC-2461: Multiple WebSecurityConfiguration instances cause null springSecurityFilterChain"() {
|
||||
setup:
|
||||
def parent = loadConfig(ParentConfig)
|
||||
def child = new AnnotationConfigApplicationContext()
|
||||
child.register(ChildConfig)
|
||||
child.parent = parent
|
||||
when:
|
||||
child.refresh()
|
||||
then: "springSecurityFilterChain can be found in parent and child"
|
||||
parent.getBean("springSecurityFilterChain")
|
||||
child.getBean("springSecurityFilterChain")
|
||||
and: "springSecurityFilterChain is defined in both parent and child (don't search parent)"
|
||||
parent.containsBeanDefinition("springSecurityFilterChain")
|
||||
child.containsBeanDefinition("springSecurityFilterChain")
|
||||
cleanup:
|
||||
child?.close()
|
||||
// parent.close() is in superclass
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ParentConfig extends WebSecurityConfigurerAdapter {
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth) {
|
||||
auth.inMemoryAuthentication()
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ChildConfig extends WebSecurityConfigurerAdapter { }
|
||||
|
||||
def "SEC-2773: delegatingApplicationListener is static method"() {
|
||||
expect: 'delegatingApplicationListener to prevent premature instantiation of WebSecurityConfiguration'
|
||||
Modifier.isStatic(WebSecurityConfiguration.metaClass.methods.find { it.name == 'delegatingApplicationListener'}.modifiers)
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -186,13 +186,14 @@ class LogoutConfigurerTests extends BaseSpringSpec {
|
||||
}
|
||||
}
|
||||
|
||||
def "LogoutConfigurer content negotiation default redirects"() {
|
||||
def "LogoutConfigurer content negotiation text/html redirects"() {
|
||||
setup:
|
||||
loadConfig(LogoutHandlerContentNegotiation)
|
||||
when:
|
||||
login()
|
||||
request.method = 'POST'
|
||||
request.servletPath = '/logout'
|
||||
request.addHeader('Accept', 'text/html')
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then:
|
||||
response.status == 302
|
||||
|
||||
-186
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.config.annotation.web.configurers
|
||||
|
||||
import org.springframework.security.core.userdetails.PasswordEncodedUser
|
||||
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.config.annotation.AnyObjectPostProcessor
|
||||
import org.springframework.security.config.annotation.BaseSpringSpec
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
||||
import org.springframework.security.web.savedrequest.RequestCache
|
||||
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
|
||||
|
||||
import spock.lang.Unroll;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class RequestCacheConfigurerTests extends BaseSpringSpec {
|
||||
|
||||
def "requestCache ObjectPostProcessor"() {
|
||||
setup:
|
||||
AnyObjectPostProcessor opp = Mock()
|
||||
HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:])
|
||||
when:
|
||||
http
|
||||
.requestCache()
|
||||
.and()
|
||||
.build()
|
||||
|
||||
then: "RequestCacheAwareFilter is registered with LifecycleManager"
|
||||
1 * opp.postProcess(_ as RequestCacheAwareFilter) >> {RequestCacheAwareFilter o -> o}
|
||||
}
|
||||
|
||||
def "invoke requestCache twice does not reset"() {
|
||||
setup:
|
||||
RequestCache RC = Mock()
|
||||
AnyObjectPostProcessor opp = Mock()
|
||||
HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:])
|
||||
when:
|
||||
http
|
||||
.requestCache()
|
||||
.requestCache(RC)
|
||||
.and()
|
||||
.requestCache()
|
||||
|
||||
then:
|
||||
http.getSharedObject(RequestCache) == RC
|
||||
}
|
||||
|
||||
def "RequestCache disables faviocon.ico"() {
|
||||
setup:
|
||||
loadConfig(RequestCacheDefautlsConfig)
|
||||
request.servletPath = "/favicon.ico"
|
||||
request.requestURI = "/favicon.ico"
|
||||
request.method = "GET"
|
||||
when: "request favicon.ico"
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: "sent to the login page"
|
||||
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
|
||||
response.redirectedUrl == "http://localhost/login"
|
||||
when: "authenticate successfully"
|
||||
super.setupWeb(request.session)
|
||||
request.servletPath = "/login"
|
||||
request.setParameter("username","user")
|
||||
request.setParameter("password","password")
|
||||
request.method = "POST"
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: "sent to default URL since it was favicon.ico"
|
||||
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
|
||||
response.redirectedUrl == "/"
|
||||
}
|
||||
|
||||
def "SEC-2321: RequestCache disables application/json"() {
|
||||
setup:
|
||||
loadConfig(RequestCacheDefautlsConfig)
|
||||
request.addHeader("Accept", MediaType.APPLICATION_JSON_VALUE)
|
||||
request.method = "GET"
|
||||
request.servletPath = "/messages"
|
||||
request.requestURI = "/messages"
|
||||
when: "request application/json"
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: "sent to the login page"
|
||||
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
|
||||
response.redirectedUrl == "http://localhost/login"
|
||||
when: "authenticate successfully"
|
||||
super.setupWeb(request.session)
|
||||
request.servletPath = "/login"
|
||||
request.setParameter("username","user")
|
||||
request.setParameter("password","password")
|
||||
request.method = "POST"
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: "sent to default URL since it was application/json. This is desirable since JSON requests are typically not invoked directly from the browser and we don't want the browser to replay them"
|
||||
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
|
||||
response.redirectedUrl == "/"
|
||||
}
|
||||
|
||||
def "SEC-2321: RequestCache disables X-Requested-With"() {
|
||||
setup:
|
||||
loadConfig(RequestCacheDefautlsConfig)
|
||||
request.addHeader("X-Requested-With", "XMLHttpRequest")
|
||||
request.method = "GET"
|
||||
request.servletPath = "/messages"
|
||||
request.requestURI = "/messages"
|
||||
when: "request X-Requested-With"
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: "sent to the login page"
|
||||
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
|
||||
response.redirectedUrl == "http://localhost/login"
|
||||
when: "authenticate successfully"
|
||||
super.setupWeb(request.session)
|
||||
request.servletPath = "/login"
|
||||
request.setParameter("username","user")
|
||||
request.setParameter("password","password")
|
||||
request.method = "POST"
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: "sent to default URL since it was X-Requested-With"
|
||||
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
|
||||
response.redirectedUrl == "/"
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "RequestCache saves #headerName: #headerValue"() {
|
||||
setup:
|
||||
loadConfig(RequestCacheDefautlsConfig)
|
||||
request.addHeader(headerName, headerValue)
|
||||
request.method = "GET"
|
||||
request.servletPath = "/messages"
|
||||
request.requestURI = "/messages"
|
||||
when: "request content type"
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
super.setupWeb(request.session)
|
||||
request.servletPath = "/login"
|
||||
request.setParameter("username","user")
|
||||
request.setParameter("password","password")
|
||||
request.method = "POST"
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: "sent to saved URL"
|
||||
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
|
||||
response.redirectedUrl == "http://localhost/messages"
|
||||
where:
|
||||
headerName << ["Accept", "Accept", "Accept", "X-Requested-With"]
|
||||
headerValue << [MediaType.ALL_VALUE, MediaType.TEXT_HTML, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8","com.android"]
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class RequestCacheDefautlsConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.formLogin()
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth
|
||||
.inMemoryAuthentication()
|
||||
.withUser(PasswordEncodedUser.user());
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
@@ -131,6 +131,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
|
||||
when: 'user cannot access otheruser'
|
||||
request = new MockHttpServletRequest(method:'GET', servletPath : '/user/otheruser/abc')
|
||||
login(request, 'user', 'password')
|
||||
response = new MockHttpServletResponse()
|
||||
chain.reset()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'The response is OK'
|
||||
@@ -138,6 +139,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
|
||||
when: 'user can access case insensitive URL'
|
||||
request = new MockHttpServletRequest(method:'GET', servletPath : '/USER/user/abc')
|
||||
login(request, 'user', 'password')
|
||||
response = new MockHttpServletResponse()
|
||||
chain.reset()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'The response is OK'
|
||||
@@ -164,6 +166,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
|
||||
when: 'user cannot access otheruser'
|
||||
request = new MockHttpServletRequest(method:'GET', servletPath : '/user/otheruser/abc')
|
||||
login(request, 'user', 'password')
|
||||
response = new MockHttpServletResponse()
|
||||
chain.reset()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'The response is OK'
|
||||
@@ -171,6 +174,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
|
||||
when: 'user can access case insensitive URL'
|
||||
request = new MockHttpServletRequest(method:'GET', servletPath : '/USER/user/abc')
|
||||
login(request, 'user', 'password')
|
||||
response = new MockHttpServletResponse()
|
||||
chain.reset()
|
||||
springSecurityFilterChain.doFilter(request,response,chain)
|
||||
then: 'The response is OK'
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ public class EnableReactiveMethodSecurityTests {
|
||||
assertThatThrownBy(() -> this.messageService.notPublisherPreAuthorizeFindById(1L))
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.extracting(Throwable::getMessage)
|
||||
.contains("The returnType class java.lang.String on public abstract java.lang.String org.springframework.security.config.annotation.method.configuration.ReactiveMessageService.notPublisherPreAuthorizeFindById(long) must return an instance of org.reactivestreams.Publisher (i.e. Mono / Flux) in order to support Reactor Context");
|
||||
.isEqualTo("The returnType class java.lang.String on public abstract java.lang.String org.springframework.security.config.annotation.method.configuration.ReactiveMessageService.notPublisherPreAuthorizeFindById(long) must return an instance of org.reactivestreams.Publisher (i.e. Mono / Flux) in order to support Reactor Context");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+406
@@ -0,0 +1,406 @@
|
||||
/*
|
||||
* 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.config.annotation.web.configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.access.expression.AbstractSecurityExpressionHandler;
|
||||
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.config.users.AuthenticationTestConfiguration;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Tests for {@link WebSecurityConfiguration}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class WebSecurityConfigurationTests {
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
public void loadConfigWhenWebSecurityConfigurersHaveOrderThenFilterChainsOrdered() throws Exception {
|
||||
this.spring.register(SortedWebSecurityConfigurerAdaptersConfig.class).autowire();
|
||||
|
||||
FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
|
||||
List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();
|
||||
assertThat(filterChains).hasSize(6);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
|
||||
request.setServletPath("/ignore1");
|
||||
assertThat(filterChains.get(0).matches(request)).isTrue();
|
||||
assertThat(filterChains.get(0).getFilters()).isEmpty();
|
||||
|
||||
request.setServletPath("/ignore2");
|
||||
assertThat(filterChains.get(1).matches(request)).isTrue();
|
||||
assertThat(filterChains.get(1).getFilters()).isEmpty();
|
||||
|
||||
request.setServletPath("/role1/**");
|
||||
assertThat(filterChains.get(2).matches(request)).isTrue();
|
||||
|
||||
request.setServletPath("/role2/**");
|
||||
assertThat(filterChains.get(3).matches(request)).isTrue();
|
||||
|
||||
request.setServletPath("/role3/**");
|
||||
assertThat(filterChains.get(4).matches(request)).isTrue();
|
||||
|
||||
request.setServletPath("/**");
|
||||
assertThat(filterChains.get(5).matches(request)).isTrue();
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(AuthenticationTestConfiguration.class)
|
||||
static class SortedWebSecurityConfigurerAdaptersConfig {
|
||||
|
||||
@Configuration
|
||||
@Order(1)
|
||||
static class WebConfigurer1 extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
web
|
||||
.ignoring()
|
||||
.antMatchers("/ignore1", "/ignore2");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.antMatcher("/role1/**")
|
||||
.authorizeRequests()
|
||||
.anyRequest().hasRole("1");
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Order(2)
|
||||
static class WebConfigurer2 extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.antMatcher("/role2/**")
|
||||
.authorizeRequests()
|
||||
.anyRequest().hasRole("2");
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Order(3)
|
||||
static class WebConfigurer3 extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.antMatcher("/role3/**")
|
||||
.authorizeRequests()
|
||||
.anyRequest().hasRole("3");
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class WebConfigurer4 extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().hasRole("4");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadConfigWhenWebSecurityConfigurersHaveSameOrderThenThrowBeanCreationException() throws Exception {
|
||||
Throwable thrown = catchThrowable(() -> this.spring.register(DuplicateOrderConfig.class).autowire());
|
||||
|
||||
assertThat(thrown).isInstanceOf(BeanCreationException.class)
|
||||
.hasMessageContaining("@Order on WebSecurityConfigurers must be unique")
|
||||
.hasMessageContaining(DuplicateOrderConfig.WebConfigurer1.class.getName())
|
||||
.hasMessageContaining(DuplicateOrderConfig.WebConfigurer2.class.getName());
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(AuthenticationTestConfiguration.class)
|
||||
static class DuplicateOrderConfig {
|
||||
|
||||
@Configuration
|
||||
static class WebConfigurer1 extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.antMatcher("/role1/**")
|
||||
.authorizeRequests()
|
||||
.anyRequest().hasRole("1");
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class WebConfigurer2 extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.antMatcher("/role2/**")
|
||||
.authorizeRequests()
|
||||
.anyRequest().hasRole("2");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadConfigWhenWebInvocationPrivilegeEvaluatorSetThenIsRegistered() throws Exception {
|
||||
PrivilegeEvaluatorConfigurerAdapterConfig.PRIVILEGE_EVALUATOR = mock(WebInvocationPrivilegeEvaluator.class);
|
||||
|
||||
this.spring.register(PrivilegeEvaluatorConfigurerAdapterConfig.class).autowire();
|
||||
|
||||
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
|
||||
.isSameAs(PrivilegeEvaluatorConfigurerAdapterConfig.PRIVILEGE_EVALUATOR);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class PrivilegeEvaluatorConfigurerAdapterConfig extends WebSecurityConfigurerAdapter {
|
||||
static WebInvocationPrivilegeEvaluator PRIVILEGE_EVALUATOR;
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
web.privilegeEvaluator(PRIVILEGE_EVALUATOR);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadConfigWhenSecurityExpressionHandlerSetThenIsRegistered() throws Exception {
|
||||
WebSecurityExpressionHandlerConfig.EXPRESSION_HANDLER = mock(SecurityExpressionHandler.class);
|
||||
when(WebSecurityExpressionHandlerConfig.EXPRESSION_HANDLER.getExpressionParser()).thenReturn(mock(ExpressionParser.class));
|
||||
|
||||
this.spring.register(WebSecurityExpressionHandlerConfig.class).autowire();
|
||||
|
||||
assertThat(this.spring.getContext().getBean(SecurityExpressionHandler.class))
|
||||
.isSameAs(WebSecurityExpressionHandlerConfig.EXPRESSION_HANDLER);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class WebSecurityExpressionHandlerConfig extends WebSecurityConfigurerAdapter {
|
||||
static SecurityExpressionHandler EXPRESSION_HANDLER;
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
web.expressionHandler(EXPRESSION_HANDLER);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
.expressionHandler(EXPRESSION_HANDLER);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadConfigWhenDefaultSecurityExpressionHandlerThenDefaultIsRegistered() throws Exception {
|
||||
this.spring.register(WebSecurityExpressionHandlerDefaultsConfig.class).autowire();
|
||||
|
||||
assertThat(this.spring.getContext().getBean(SecurityExpressionHandler.class))
|
||||
.isInstanceOf(DefaultWebSecurityExpressionHandler.class);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class WebSecurityExpressionHandlerDefaultsConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securityExpressionHandlerWhenPermissionEvaluatorBeanThenPermissionEvaluatorUsed() throws Exception {
|
||||
this.spring.register(WebSecurityExpressionHandlerPermissionEvaluatorBeanConfig.class).autowire();
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "notused");
|
||||
FilterInvocation invocation = new FilterInvocation(new MockHttpServletRequest(), new MockHttpServletResponse(), new MockFilterChain());
|
||||
|
||||
AbstractSecurityExpressionHandler handler = this.spring.getContext().getBean(AbstractSecurityExpressionHandler.class);
|
||||
EvaluationContext evaluationContext = handler.createEvaluationContext(authentication, invocation);
|
||||
Expression expression = handler.getExpressionParser()
|
||||
.parseExpression("hasPermission(#study,'DELETE')");
|
||||
boolean granted = expression.getValue(evaluationContext, Boolean.class);
|
||||
assertThat(granted).isTrue();
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class WebSecurityExpressionHandlerPermissionEvaluatorBeanConfig extends WebSecurityConfigurerAdapter {
|
||||
static final PermissionEvaluator PERMIT_ALL_PERMISSION_EVALUATOR = new PermissionEvaluator() {
|
||||
@Override
|
||||
public boolean hasPermission(Authentication authentication,
|
||||
Object targetDomainObject, Object permission) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(Authentication authentication,
|
||||
Serializable targetId, String targetType, Object permission) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@Bean
|
||||
public PermissionEvaluator permissionEvaluator() {
|
||||
return PERMIT_ALL_PERMISSION_EVALUATOR;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadConfigWhenDefaultWebInvocationPrivilegeEvaluatorThenDefaultIsRegistered() throws Exception {
|
||||
this.spring.register(WebInvocationPrivilegeEvaluatorDefaultsConfig.class).autowire();
|
||||
|
||||
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
|
||||
.isInstanceOf(DefaultWebInvocationPrivilegeEvaluator.class);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class WebInvocationPrivilegeEvaluatorDefaultsConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated();
|
||||
}
|
||||
}
|
||||
|
||||
// SEC-2303
|
||||
@Test
|
||||
public void loadConfigWhenDefaultSecurityExpressionHandlerThenBeanResolverSet() throws Exception {
|
||||
this.spring.register(DefaultExpressionHandlerSetsBeanResolverConfig.class).autowire();
|
||||
|
||||
this.mockMvc.perform(get("/")).andExpect(status().isOk());
|
||||
this.mockMvc.perform(post("/")).andExpect(status().isForbidden());
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class DefaultExpressionHandlerSetsBeanResolverConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().access("request.method == 'GET' ? @b.grant() : @b.deny()");
|
||||
}
|
||||
|
||||
@RestController
|
||||
public class HomeController {
|
||||
@GetMapping("/")
|
||||
public String home() {
|
||||
return "home";
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MyBean b() {
|
||||
return new MyBean();
|
||||
}
|
||||
|
||||
static class MyBean {
|
||||
public boolean deny() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean grant() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Rule
|
||||
public SpringTestRule child = new SpringTestRule();
|
||||
|
||||
// SEC-2461
|
||||
@Test
|
||||
public void loadConfigWhenMultipleWebSecurityConfigurationThenContextLoads() throws Exception {
|
||||
this.spring.register(ParentConfig.class).autowire();
|
||||
|
||||
this.child.register(ChildConfig.class);
|
||||
this.child.getContext().setParent(this.spring.getContext());
|
||||
this.child.autowire();
|
||||
|
||||
assertThat(this.spring.getContext().getBean("springSecurityFilterChain")).isNotNull();
|
||||
assertThat(this.child.getContext().getBean("springSecurityFilterChain")).isNotNull();
|
||||
|
||||
assertThat(this.spring.getContext().containsBean("springSecurityFilterChain")).isTrue();
|
||||
assertThat(this.child.getContext().containsBean("springSecurityFilterChain")).isTrue();
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ParentConfig extends WebSecurityConfigurerAdapter {
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.inMemoryAuthentication();
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ChildConfig extends WebSecurityConfigurerAdapter {
|
||||
}
|
||||
|
||||
// SEC-2773
|
||||
@Test
|
||||
public void getMethodDelegatingApplicationListenerWhenWebSecurityConfigurationThenIsStatic() throws Exception {
|
||||
Method method = ClassUtils.getMethod(WebSecurityConfiguration.class, "delegatingApplicationListener", null);
|
||||
assertThat(Modifier.isStatic(method.getModifiers())).isTrue();
|
||||
}
|
||||
}
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* 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.config.annotation.web.configurers;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockHttpSession;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.savedrequest.RequestCache;
|
||||
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.RequestBuilder;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||
|
||||
/**
|
||||
* Tests for {@link RequestCacheConfigurer}
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
public class RequestCacheConfigurerTests {
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
|
||||
@Autowired
|
||||
MockMvc mvc;
|
||||
|
||||
@Test
|
||||
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnExceptionTranslationFilter() {
|
||||
this.spring.register(ObjectPostProcessorConfig.class, DefaultSecurityConfig.class).autowire();
|
||||
|
||||
verify(ObjectPostProcessorConfig.objectPostProcessor)
|
||||
.postProcess(any(RequestCacheAwareFilter.class));
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter {
|
||||
static ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestCache();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
static ObjectPostProcessor<Object> objectPostProcessor() {
|
||||
return objectPostProcessor;
|
||||
}
|
||||
}
|
||||
|
||||
static class ReflectingObjectPostProcessor implements ObjectPostProcessor<Object> {
|
||||
@Override
|
||||
public <O> O postProcess(O object) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenInvokingExceptionHandlingTwiceThenOriginalEntryPointUsed() throws Exception {
|
||||
this.spring.register(InvokeTwiceDoesNotOverrideConfig.class).autowire();
|
||||
|
||||
this.mvc.perform(get("/"));
|
||||
|
||||
verify(InvokeTwiceDoesNotOverrideConfig.requestCache)
|
||||
.getMatchingRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class InvokeTwiceDoesNotOverrideConfig extends WebSecurityConfigurerAdapter {
|
||||
static RequestCache requestCache = mock(RequestCache.class);
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestCache()
|
||||
.requestCache(requestCache)
|
||||
.and()
|
||||
.requestCache();
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenBookmarkedUrlIsFaviconIcoThenPostAuthenticationRedirectsToRoot() throws Exception {
|
||||
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
|
||||
|
||||
MockHttpSession session = (MockHttpSession)
|
||||
this.mvc.perform(get("/favicon.ico"))
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andReturn().getRequest().getSession();
|
||||
|
||||
this.mvc.perform(formLogin(session))
|
||||
.andExpect(redirectedUrl("/")); // ignores favicon.ico
|
||||
}
|
||||
|
||||
// SEC-2321
|
||||
@Test
|
||||
public void getWhenBookmarkedRequestIsApplicationJsonThenPostAuthenticationRedirectsToRoot() throws Exception {
|
||||
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
|
||||
|
||||
MockHttpSession session = (MockHttpSession)
|
||||
this.mvc.perform(get("/messages")
|
||||
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON))
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andReturn().getRequest().getSession();
|
||||
|
||||
this.mvc.perform(formLogin(session))
|
||||
.andExpect(redirectedUrl("/")); // ignores application/json
|
||||
|
||||
// This is desirable since JSON requests are typically not invoked directly from the browser and we don't want the browser to replay them
|
||||
}
|
||||
|
||||
// SEC-2321
|
||||
@Test
|
||||
public void getWhenBookmarkedRequestIsXRequestedWithThenPostAuthenticationRedirectsToRoot() throws Exception {
|
||||
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
|
||||
|
||||
MockHttpSession session = (MockHttpSession)
|
||||
this.mvc.perform(get("/messages")
|
||||
.header("X-Requested-With", "XMLHttpRequest"))
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andReturn().getRequest().getSession();
|
||||
|
||||
this.mvc.perform(formLogin(session))
|
||||
.andExpect(redirectedUrl("/"));
|
||||
|
||||
// This is desirable since XHR requests are typically not invoked directly from the browser and we don't want the browser to replay them
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemembers() throws Exception {
|
||||
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
|
||||
|
||||
MockHttpSession session = (MockHttpSession)
|
||||
this.mvc.perform(get("/messages")
|
||||
.header(HttpHeaders.ACCEPT, MediaType.ALL))
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andReturn().getRequest().getSession();
|
||||
|
||||
this.mvc.perform(formLogin(session))
|
||||
.andExpect(redirectedUrl("http://localhost/messages"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenBookmarkedRequestIsTextHtmlThenPostAuthenticationRemembers() throws Exception {
|
||||
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
|
||||
|
||||
MockHttpSession session = (MockHttpSession)
|
||||
this.mvc.perform(get("/messages")
|
||||
.header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML))
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andReturn().getRequest().getSession();
|
||||
|
||||
this.mvc.perform(formLogin(session))
|
||||
.andExpect(redirectedUrl("http://localhost/messages"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenBookmarkedRequestIsChromeThenPostAuthenticationRemembers() throws Exception {
|
||||
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
|
||||
|
||||
MockHttpSession session = (MockHttpSession)
|
||||
this.mvc.perform(get("/messages")
|
||||
.header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"))
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andReturn().getRequest().getSession();
|
||||
|
||||
this.mvc.perform(formLogin(session))
|
||||
.andExpect(redirectedUrl("http://localhost/messages"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenBookmarkedRequestIsRequestedWithAndroidThenPostAuthenticationRemembers() throws Exception {
|
||||
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
|
||||
|
||||
MockHttpSession session = (MockHttpSession)
|
||||
this.mvc.perform(get("/messages")
|
||||
.header("X-Requested-With", "com.android"))
|
||||
.andExpect(redirectedUrl("http://localhost/login"))
|
||||
.andReturn().getRequest().getSession();
|
||||
|
||||
this.mvc.perform(formLogin(session))
|
||||
.andExpect(redirectedUrl("http://localhost/messages"));
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class RequestCacheDefaultsConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.formLogin();
|
||||
}
|
||||
}
|
||||
|
||||
// gh-6102
|
||||
@Test
|
||||
public void getWhenRequestCacheIsDisabledThenExceptionTranslationFilterDoesNotStoreRequest() throws Exception {
|
||||
this.spring.register(RequestCacheDisabledConfig.class, DefaultSecurityConfig.class).autowire();
|
||||
|
||||
MockHttpSession session = (MockHttpSession)
|
||||
this.mvc.perform(get("/bob"))
|
||||
.andReturn().getRequest().getSession();
|
||||
|
||||
this.mvc.perform(formLogin(session))
|
||||
.andExpect(redirectedUrl("/"));
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class RequestCacheDisabledConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
super.configure(http);
|
||||
http.requestCache().disable();
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class DefaultSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public InMemoryUserDetailsManager userDetailsManager() {
|
||||
return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static RequestBuilder formLogin(MockHttpSession session) {
|
||||
return post("/login")
|
||||
.param("username", "user")
|
||||
.param("password", "password")
|
||||
.session(session)
|
||||
.with(csrf());
|
||||
}
|
||||
}
|
||||
+75
-1
@@ -20,11 +20,13 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.PropertyAccessorFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
@@ -145,6 +147,30 @@ public class OAuth2LoginConfigurerTests {
|
||||
.isInstanceOf(OAuth2UserAuthority.class).hasToString("ROLE_USER");
|
||||
}
|
||||
|
||||
// gh-6009
|
||||
@Test
|
||||
public void oauth2LoginWhenSuccessThenAuthenticationSuccessEventPublished() throws Exception {
|
||||
// setup application context
|
||||
loadConfig(OAuth2LoginConfig.class);
|
||||
|
||||
// setup authorization request
|
||||
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest();
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(
|
||||
authorizationRequest, this.request, this.response);
|
||||
|
||||
// setup authentication parameters
|
||||
this.request.setParameter("code", "code123");
|
||||
this.request.setParameter("state", authorizationRequest.getState());
|
||||
|
||||
// perform test
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
|
||||
|
||||
// assertions
|
||||
assertThat(OAuth2LoginConfig.EVENTS).isNotEmpty();
|
||||
assertThat(OAuth2LoginConfig.EVENTS).hasSize(1);
|
||||
assertThat(OAuth2LoginConfig.EVENTS.get(0)).isInstanceOf(AuthenticationSuccessEvent.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oauth2LoginCustomWithConfigurer() throws Exception {
|
||||
// setup application context
|
||||
@@ -197,6 +223,34 @@ public class OAuth2LoginConfigurerTests {
|
||||
assertThat(authentication.getAuthorities()).last().hasToString("ROLE_OAUTH2_USER");
|
||||
}
|
||||
|
||||
// gh-5488
|
||||
@Test
|
||||
public void oauth2LoginConfigLoginProcessingUrl() throws Exception {
|
||||
// setup application context
|
||||
loadConfig(OAuth2LoginConfigLoginProcessingUrl.class);
|
||||
|
||||
// setup authorization request
|
||||
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest();
|
||||
this.request.setServletPath("/login/oauth2/google");
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(
|
||||
authorizationRequest, this.request, this.response);
|
||||
|
||||
// setup authentication parameters
|
||||
this.request.setParameter("code", "code123");
|
||||
this.request.setParameter("state", authorizationRequest.getState());
|
||||
|
||||
// perform test
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
|
||||
|
||||
// assertions
|
||||
Authentication authentication = this.securityContextRepository
|
||||
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
|
||||
.getAuthentication();
|
||||
assertThat(authentication.getAuthorities()).hasSize(1);
|
||||
assertThat(authentication.getAuthorities()).first()
|
||||
.isInstanceOf(OAuth2UserAuthority.class).hasToString("ROLE_USER");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oidcLogin() throws Exception {
|
||||
// setup application context
|
||||
@@ -320,7 +374,9 @@ public class OAuth2LoginConfigurerTests {
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class OAuth2LoginConfig extends CommonWebSecurityConfigurerAdapter {
|
||||
static class OAuth2LoginConfig extends CommonWebSecurityConfigurerAdapter implements ApplicationListener<AuthenticationSuccessEvent> {
|
||||
static List<AuthenticationSuccessEvent> EVENTS = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
@@ -329,6 +385,11 @@ public class OAuth2LoginConfigurerTests {
|
||||
new InMemoryClientRegistrationRepository(CLIENT_REGISTRATION));
|
||||
super.configure(http);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(AuthenticationSuccessEvent event) {
|
||||
EVENTS.add(event);
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@@ -365,6 +426,19 @@ public class OAuth2LoginConfigurerTests {
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class OAuth2LoginConfigLoginProcessingUrl extends CommonWebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2Login()
|
||||
.clientRegistrationRepository(
|
||||
new InMemoryClientRegistrationRepository(CLIENT_REGISTRATION))
|
||||
.loginProcessingUrl("/login/oauth2/*");
|
||||
super.configure(http);
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class CommonWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.config.web.server;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder;
|
||||
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler;
|
||||
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
|
||||
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
|
||||
|
||||
/**
|
||||
* @author Denys Ivano
|
||||
* @since 5.0.5
|
||||
*/
|
||||
public class ExceptionHandlingSpecTests {
|
||||
private ServerHttpSecurity http = ServerHttpSecurityConfigurationBuilder.httpWithDefaultAuthentication();
|
||||
|
||||
@Test
|
||||
public void defaultAuthenticationEntryPoint() {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.csrf().disable()
|
||||
.authorizeExchange()
|
||||
.anyExchange().authenticated()
|
||||
.and()
|
||||
.exceptionHandling()
|
||||
.and()
|
||||
.build();
|
||||
|
||||
WebTestClient client = WebTestClientBuilder
|
||||
.bindToWebFilters(securityWebFilter)
|
||||
.build();
|
||||
|
||||
client
|
||||
.get()
|
||||
.uri("/test")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized()
|
||||
.expectHeader().valueMatches("WWW-Authenticate", "Basic.*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customAuthenticationEntryPoint() {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.csrf().disable()
|
||||
.authorizeExchange()
|
||||
.anyExchange().authenticated()
|
||||
.and()
|
||||
.exceptionHandling()
|
||||
.authenticationEntryPoint(redirectServerAuthenticationEntryPoint("/auth"))
|
||||
.and()
|
||||
.build();
|
||||
|
||||
WebTestClient client = WebTestClientBuilder
|
||||
.bindToWebFilters(securityWebFilter)
|
||||
.build();
|
||||
|
||||
client
|
||||
.get()
|
||||
.uri("/test")
|
||||
.exchange()
|
||||
.expectStatus().isFound()
|
||||
.expectHeader().valueMatches("Location", ".*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultAccessDeniedHandler() {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.csrf().disable()
|
||||
.httpBasic().and()
|
||||
.authorizeExchange()
|
||||
.anyExchange().hasRole("ADMIN")
|
||||
.and()
|
||||
.exceptionHandling()
|
||||
.and()
|
||||
.build();
|
||||
|
||||
WebTestClient client = WebTestClientBuilder
|
||||
.bindToWebFilters(securityWebFilter)
|
||||
.filter(basicAuthentication())
|
||||
.build();
|
||||
|
||||
client
|
||||
.get()
|
||||
.uri("/admin")
|
||||
.attributes(basicAuthenticationCredentials("user", "password"))
|
||||
.exchange()
|
||||
.expectStatus().isForbidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customAccessDeniedHandler() {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.csrf().disable()
|
||||
.httpBasic().and()
|
||||
.authorizeExchange()
|
||||
.anyExchange().hasRole("ADMIN")
|
||||
.and()
|
||||
.exceptionHandling()
|
||||
.accessDeniedHandler(httpStatusServerAccessDeniedHandler(HttpStatus.BAD_REQUEST))
|
||||
.and()
|
||||
.build();
|
||||
|
||||
WebTestClient client = WebTestClientBuilder
|
||||
.bindToWebFilters(securityWebFilter)
|
||||
.filter(basicAuthentication())
|
||||
.build();
|
||||
|
||||
client
|
||||
.get()
|
||||
.uri("/admin")
|
||||
.attributes(basicAuthenticationCredentials("user", "password"))
|
||||
.exchange()
|
||||
.expectStatus().isBadRequest();
|
||||
}
|
||||
|
||||
private ServerAuthenticationEntryPoint redirectServerAuthenticationEntryPoint(String location) {
|
||||
return new RedirectServerAuthenticationEntryPoint(location);
|
||||
}
|
||||
|
||||
private ServerAccessDeniedHandler httpStatusServerAccessDeniedHandler(HttpStatus httpStatus) {
|
||||
return new HttpStatusServerAccessDeniedHandler(httpStatus);
|
||||
}
|
||||
}
|
||||
+42
-16
@@ -28,7 +28,7 @@ import org.springframework.test.web.reactive.server.FluxExchangeResult;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -45,7 +45,7 @@ public class HeaderSpecTests {
|
||||
|
||||
HttpHeaders expectedHeaders = new HttpHeaders();
|
||||
|
||||
Set<String> ignoredHeaderNames = Collections.singleton(HttpHeaders.CONTENT_TYPE);
|
||||
Set<String> headerNamesNotPresent = new HashSet<>();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
@@ -60,6 +60,23 @@ public class HeaderSpecTests {
|
||||
.add(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenDisableThenNoSecurityHeaders() {
|
||||
new HashSet<>(this.expectedHeaders.keySet()).forEach(this::expectHeaderNamesNotPresent);
|
||||
|
||||
this.headers.disable();
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenDisableAndInvokedExplicitlyThenDefautsUsed() {
|
||||
this.headers.disable()
|
||||
.headers();
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenDefaultsThenAllDefaultsWritten() {
|
||||
assertHeaders();
|
||||
@@ -67,9 +84,7 @@ public class HeaderSpecTests {
|
||||
|
||||
@Test
|
||||
public void headersWhenCacheDisableThenCacheNotWritten() {
|
||||
this.expectedHeaders.remove(HttpHeaders.CACHE_CONTROL);
|
||||
this.expectedHeaders.remove(HttpHeaders.PRAGMA);
|
||||
this.expectedHeaders.remove(HttpHeaders.EXPIRES);
|
||||
expectHeaderNamesNotPresent(HttpHeaders.CACHE_CONTROL, HttpHeaders.PRAGMA, HttpHeaders.EXPIRES);
|
||||
this.headers.cache().disable();
|
||||
|
||||
assertHeaders();
|
||||
@@ -77,7 +92,7 @@ public class HeaderSpecTests {
|
||||
|
||||
@Test
|
||||
public void headersWhenContentOptionsDisableThenContentTypeOptionsNotWritten() {
|
||||
this.expectedHeaders.remove(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS);
|
||||
expectHeaderNamesNotPresent(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS);
|
||||
this.headers.contentTypeOptions().disable();
|
||||
|
||||
assertHeaders();
|
||||
@@ -85,7 +100,7 @@ public class HeaderSpecTests {
|
||||
|
||||
@Test
|
||||
public void headersWhenHstsDisableThenHstsNotWritten() {
|
||||
this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
|
||||
expectHeaderNamesNotPresent(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
|
||||
this.headers.hsts().disable();
|
||||
|
||||
assertHeaders();
|
||||
@@ -103,7 +118,7 @@ public class HeaderSpecTests {
|
||||
|
||||
@Test
|
||||
public void headersWhenFrameOptionsDisableThenFrameOptionsNotWritten() {
|
||||
this.expectedHeaders.remove(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS);
|
||||
expectHeaderNamesNotPresent(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS);
|
||||
this.headers.frameOptions().disable();
|
||||
|
||||
assertHeaders();
|
||||
@@ -111,22 +126,29 @@ public class HeaderSpecTests {
|
||||
|
||||
@Test
|
||||
public void headersWhenFrameOptionsModeThenFrameOptionsCustomMode() {
|
||||
this.expectedHeaders.remove(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS);
|
||||
this.expectedHeaders
|
||||
.add(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "SAMEORIGIN");
|
||||
this.headers.frameOptions().mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN);
|
||||
this.expectedHeaders.set(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "SAMEORIGIN");
|
||||
this.headers
|
||||
.frameOptions()
|
||||
.mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenXssProtectionDisableThenXssProtectionNotWritten() {
|
||||
this.expectedHeaders.remove("X-Xss-Protection");
|
||||
expectHeaderNamesNotPresent("X-Xss-Protection");
|
||||
this.headers.xssProtection().disable();
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
private void expectHeaderNamesNotPresent(String... headerNames) {
|
||||
for(String headerName : headerNames) {
|
||||
this.expectedHeaders.remove(headerName);
|
||||
this.headerNamesNotPresent.add(headerName);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertHeaders() {
|
||||
WebTestClient client = buildClient();
|
||||
FluxExchangeResult<String> response = client.get()
|
||||
@@ -135,10 +157,14 @@ public class HeaderSpecTests {
|
||||
.returnResult(String.class);
|
||||
|
||||
Map<String, List<String>> responseHeaders = response.getResponseHeaders();
|
||||
this.ignoredHeaderNames.stream().forEach(responseHeaders::remove);
|
||||
|
||||
assertThat(responseHeaders).describedAs(response.toString()).isEqualTo(
|
||||
this.expectedHeaders);
|
||||
if (!this.expectedHeaders.isEmpty()) {
|
||||
assertThat(responseHeaders).describedAs(response.toString())
|
||||
.containsAllEntriesOf(this.expectedHeaders);
|
||||
}
|
||||
if (!this.headerNamesNotPresent.isEmpty()) {
|
||||
assertThat(responseHeaders.keySet()).doesNotContainAnyElementsOf(this.headerNamesNotPresent);
|
||||
}
|
||||
}
|
||||
|
||||
private WebTestClient buildClient() {
|
||||
|
||||
@@ -34,6 +34,17 @@ dependencies {
|
||||
testRuntime 'org.hsqldb:hsqldb'
|
||||
}
|
||||
|
||||
task springVersion(type: versions.VersionsResourceTasks) {
|
||||
versionsFile = file("${buildDir}/versions/spring-security.versions")
|
||||
versions = { project.dependencyManagement.managedVersions }
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
into('META-INF') {
|
||||
from project.tasks.springVersion.outputs
|
||||
}
|
||||
}
|
||||
|
||||
tasks.jar.from { includeProject.sourceSets.main.output }
|
||||
|
||||
tasks.sourcesJar.from {includeProject.sourceSets.main.java}
|
||||
|
||||
@@ -55,5 +55,5 @@ public @interface Secured {
|
||||
*
|
||||
* @return String[] The secure method attributes
|
||||
*/
|
||||
public String[]value();
|
||||
public String[] value();
|
||||
}
|
||||
|
||||
+3
-1
@@ -112,7 +112,9 @@ class MethodSecurityEvaluationContext extends StandardEvaluationContext {
|
||||
}
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
super.setVariable(paramNames[i], args[i]);
|
||||
if (paramNames[i] != null) {
|
||||
setVariable(paramNames[i], args[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
@@ -36,6 +36,9 @@ import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A {@link MethodInterceptor} that supports {@link PreAuthorize} and {@link PostAuthorize} for methods that return
|
||||
* {@link Mono} or {@link Flux}
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@@ -49,6 +52,12 @@ public class PrePostAdviceReactiveMethodInterceptor implements MethodInterceptor
|
||||
|
||||
private final PostInvocationAuthorizationAdvice postAdvice;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param attributeSource the {@link MethodSecurityMetadataSource} to use
|
||||
* @param preInvocationAdvice the {@link PreInvocationAuthorizationAdvice} to use
|
||||
* @param postInvocationAdvice the {@link PostInvocationAuthorizationAdvice} to use
|
||||
*/
|
||||
public PrePostAdviceReactiveMethodInterceptor(MethodSecurityMetadataSource attributeSource, PreInvocationAuthorizationAdvice preInvocationAdvice, PostInvocationAuthorizationAdvice postInvocationAdvice) {
|
||||
Assert.notNull(attributeSource, "attributeSource cannot be null");
|
||||
Assert.notNull(preInvocationAdvice, "preInvocationAdvice cannot be null");
|
||||
|
||||
+15
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
* 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.
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.authentication;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -157,7 +156,9 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
||||
throws AuthenticationException {
|
||||
Class<? extends Authentication> toTest = authentication.getClass();
|
||||
AuthenticationException lastException = null;
|
||||
AuthenticationException parentException = null;
|
||||
Authentication result = null;
|
||||
Authentication parentResult = null;
|
||||
boolean debug = logger.isDebugEnabled();
|
||||
|
||||
for (AuthenticationProvider provider : getProviders()) {
|
||||
@@ -196,7 +197,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
||||
if (result == null && parent != null) {
|
||||
// Allow the parent to try.
|
||||
try {
|
||||
result = parent.authenticate(authentication);
|
||||
result = parentResult = parent.authenticate(authentication);
|
||||
}
|
||||
catch (ProviderNotFoundException e) {
|
||||
// ignore as we will throw below if no other exception occurred prior to
|
||||
@@ -205,7 +206,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
||||
// handled the request
|
||||
}
|
||||
catch (AuthenticationException e) {
|
||||
lastException = e;
|
||||
lastException = parentException = e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +218,11 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
||||
((CredentialsContainer) result).eraseCredentials();
|
||||
}
|
||||
|
||||
eventPublisher.publishAuthenticationSuccess(result);
|
||||
// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
|
||||
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
|
||||
if (parentResult == null) {
|
||||
eventPublisher.publishAuthenticationSuccess(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -230,7 +235,11 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
||||
"No AuthenticationProvider found for {0}"));
|
||||
}
|
||||
|
||||
prepareException(lastException, authentication);
|
||||
// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
|
||||
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
|
||||
if (parentException == null) {
|
||||
prepareException(lastException, authentication);
|
||||
}
|
||||
|
||||
throw lastException;
|
||||
}
|
||||
|
||||
+1
@@ -25,6 +25,7 @@ import reactor.core.publisher.Mono;
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReactiveAuthenticationManager {
|
||||
|
||||
/**
|
||||
|
||||
+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
|
||||
|
||||
+28
-1
@@ -23,9 +23,13 @@ import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
/**
|
||||
* A {@link ReactiveAuthenticationManager} that uses a {@link ReactiveUserDetailsService} to validate the provided
|
||||
* username and password.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@@ -34,6 +38,8 @@ public class UserDetailsRepositoryReactiveAuthenticationManager implements React
|
||||
|
||||
private PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
||||
|
||||
private Scheduler scheduler = Schedulers.parallel();
|
||||
|
||||
public UserDetailsRepositoryReactiveAuthenticationManager(ReactiveUserDetailsService userDetailsService) {
|
||||
Assert.notNull(userDetailsService, "userDetailsService cannot be null");
|
||||
this.userDetailsService = userDetailsService;
|
||||
@@ -43,14 +49,35 @@ public class UserDetailsRepositoryReactiveAuthenticationManager implements React
|
||||
public Mono<Authentication> authenticate(Authentication authentication) {
|
||||
final String username = authentication.getName();
|
||||
return this.userDetailsService.findByUsername(username)
|
||||
.publishOn(Schedulers.parallel())
|
||||
.publishOn(this.scheduler)
|
||||
.filter( u -> this.passwordEncoder.matches((String) authentication.getCredentials(), u.getPassword()))
|
||||
.switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException("Invalid Credentials"))))
|
||||
.map( u -> new UsernamePasswordAuthenticationToken(u, u.getPassword(), u.getAuthorities()) );
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link PasswordEncoder} that is used for validating the password. The default is
|
||||
* {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}
|
||||
* @param passwordEncoder the {@link PasswordEncoder} to use. Cannot be null
|
||||
*/
|
||||
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
|
||||
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Scheduler} used by the {@link UserDetailsRepositoryReactiveAuthenticationManager}.
|
||||
* The default is {@code Schedulers.parallel()} because modern password encoding is
|
||||
* a CPU intensive task that is non blocking. This means validation is bounded by the
|
||||
* number of CPUs. Some applications may want to customize the {@link Scheduler}. For
|
||||
* example, if users are stuck using the insecure {@link org.springframework.security.crypto.password.NoOpPasswordEncoder}
|
||||
* they might want to leverage {@code Schedulers.immediate()}.
|
||||
*
|
||||
* @param scheduler the {@link Scheduler} to use. Cannot be null.
|
||||
* @since 5.0.6
|
||||
*/
|
||||
public void setScheduler(Scheduler scheduler) {
|
||||
Assert.notNull(scheduler, "scheduler cannot be null");
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
}
|
||||
|
||||
+21
-1
@@ -20,6 +20,9 @@ import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.SpringVersion;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Internal class used for checking version compatibility in a deployed application.
|
||||
*
|
||||
@@ -40,7 +43,7 @@ public class SpringSecurityCoreVersion {
|
||||
*/
|
||||
public static final long SERIAL_VERSION_UID = 500L;
|
||||
|
||||
static final String MIN_SPRING_VERSION = "5.0.4.RELEASE";
|
||||
static final String MIN_SPRING_VERSION = getSpringVersion();
|
||||
|
||||
static {
|
||||
performVersionChecks();
|
||||
@@ -64,6 +67,9 @@ public class SpringSecurityCoreVersion {
|
||||
* @param minSpringVersion
|
||||
*/
|
||||
private static void performVersionChecks(String minSpringVersion) {
|
||||
if (minSpringVersion == null) {
|
||||
return;
|
||||
}
|
||||
// Check Spring Compatibility
|
||||
String springVersion = SpringVersion.getVersion();
|
||||
String version = getVersion();
|
||||
@@ -95,4 +101,18 @@ public class SpringSecurityCoreVersion {
|
||||
}
|
||||
return Boolean.getBoolean(DISABLE_CHECKS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the spring version or null if it cannot be found.
|
||||
* @return
|
||||
*/
|
||||
private static String getSpringVersion() {
|
||||
Properties properties = new Properties();
|
||||
try {
|
||||
properties.load(SpringSecurityCoreVersion.class.getClassLoader().getResourceAsStream("META-INF/spring-security.versions"));
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
return properties.getProperty("org.springframework:spring-core");
|
||||
}
|
||||
}
|
||||
|
||||
+1
-11
@@ -132,17 +132,7 @@ public class SessionRegistryImpl implements SessionRegistry,
|
||||
sessionIds.put(sessionId,
|
||||
new SessionInformation(principal, sessionId, new Date()));
|
||||
|
||||
Set<String> sessionsUsedByPrincipal = principals.get(principal);
|
||||
|
||||
if (sessionsUsedByPrincipal == null) {
|
||||
sessionsUsedByPrincipal = new CopyOnWriteArraySet<>();
|
||||
Set<String> prevSessionsUsedByPrincipal = principals.putIfAbsent(principal,
|
||||
sessionsUsedByPrincipal);
|
||||
if (prevSessionsUsedByPrincipal != null) {
|
||||
sessionsUsedByPrincipal = prevSessionsUsedByPrincipal;
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> sessionsUsedByPrincipal = principals.computeIfAbsent(principal, key -> new CopyOnWriteArraySet<>());
|
||||
sessionsUsedByPrincipal.add(sessionId);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
|
||||
+13
@@ -26,6 +26,7 @@ import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* A {@link Map} based implementation of {@link ReactiveUserDetailsService}
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
@@ -33,14 +34,26 @@ import reactor.core.publisher.Mono;
|
||||
public class MapReactiveUserDetailsService implements ReactiveUserDetailsService {
|
||||
private final Map<String, UserDetails> users;
|
||||
|
||||
/**
|
||||
* Creates a new instance using a {@link Map} that must be non blocking.
|
||||
* @param users a {@link Map} of users to use.
|
||||
*/
|
||||
public MapReactiveUserDetailsService(Map<String, UserDetails> users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param users the {@link UserDetails} to use
|
||||
*/
|
||||
public MapReactiveUserDetailsService(UserDetails... users) {
|
||||
this(Arrays.asList(users));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param users the {@link UserDetails} to use
|
||||
*/
|
||||
public MapReactiveUserDetailsService(Collection<UserDetails> users) {
|
||||
Assert.notEmpty(users, "users cannot be null or empty");
|
||||
this.users = users.stream().collect(Collectors.toConcurrentMap( u -> getKey(u.getUsername()), Function.identity()));
|
||||
|
||||
+11
-2
@@ -16,11 +16,20 @@
|
||||
|
||||
package org.springframework.security.core.userdetails;
|
||||
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* An API for finding the {@link UserDetails} by username.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface ReactiveUserDetailsService {
|
||||
|
||||
/**
|
||||
* Find the {@link UserDetails} by username.
|
||||
* @param username the username to look up
|
||||
* @return the {@link UserDetails}. Cannot be null
|
||||
*/
|
||||
Mono<UserDetails> findByUsername(String username);
|
||||
}
|
||||
|
||||
@@ -326,7 +326,10 @@ public class User implements UserDetails, CredentialsContainer {
|
||||
* @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();
|
||||
|
||||
+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
|
||||
*
|
||||
|
||||
+15
-3
@@ -18,16 +18,28 @@ package org.springframework.security.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotation;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import com.fasterxml.jackson.databind.DatabindContext;
|
||||
import com.fasterxml.jackson.databind.DeserializationConfig;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.cfg.MapperConfig;
|
||||
import com.fasterxml.jackson.databind.jsontype.*;
|
||||
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
|
||||
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.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This utility class will find all the SecurityModules in classpath.
|
||||
|
||||
+8
@@ -37,6 +37,7 @@ import org.springframework.util.Assert;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
@@ -122,6 +123,13 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa
|
||||
|
||||
private UserCache userCache = new NullUserCache();
|
||||
|
||||
public JdbcUserDetailsManager() {
|
||||
}
|
||||
|
||||
public JdbcUserDetailsManager(DataSource dataSource) {
|
||||
setDataSource(dataSource);
|
||||
}
|
||||
|
||||
// ~ Methods
|
||||
// ========================================================================================================
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
AbstractAccessDecisionManager.accessDenied=\u4E0D\u5141\u8BB8\u8BBF\u95EE
|
||||
AbstractLdapAuthenticationProvider.emptyPassword=\u574F\u7684\u51ED\u8BC1
|
||||
AbstractLdapAuthenticationProvider.emptyPassword=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
|
||||
AbstractSecurityInterceptor.authenticationNotFound=\u672A\u5728SecurityContext\u4E2D\u67E5\u627E\u5230\u8BA4\u8BC1\u5BF9\u8C61
|
||||
AbstractUserDetailsAuthenticationProvider.badCredentials=\u574F\u7684\u51ED\u8BC1
|
||||
AbstractUserDetailsAuthenticationProvider.badCredentials=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
|
||||
AbstractUserDetailsAuthenticationProvider.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
|
||||
AbstractUserDetailsAuthenticationProvider.disabled=\u7528\u6237\u5DF2\u5931\u6548
|
||||
AbstractUserDetailsAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
|
||||
@@ -13,8 +13,8 @@ AccountStatusUserDetailsChecker.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u67
|
||||
AccountStatusUserDetailsChecker.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
|
||||
AclEntryAfterInvocationProvider.noPermission=\u7ED9\u5B9A\u7684Authentication\u5BF9\u8C61({0})\u6839\u672C\u65E0\u6743\u64CD\u63A7\u9886\u57DF\u5BF9\u8C61({1})
|
||||
AnonymousAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684AnonymousAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
|
||||
BindAuthenticator.badCredentials=\u574F\u7684\u51ED\u8BC1
|
||||
BindAuthenticator.emptyPassword=\u574F\u7684\u51ED\u8BC1
|
||||
BindAuthenticator.badCredentials=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
|
||||
BindAuthenticator.emptyPassword=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
|
||||
CasAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684CasAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
|
||||
CasAuthenticationProvider.noServiceTicket=\u672A\u80FD\u591F\u6B63\u786E\u63D0\u4F9B\u5F85\u9A8C\u8BC1\u7684CAS\u670D\u52A1\u7968\u6839
|
||||
ConcurrentSessionControlAuthenticationStrategy.exceededAllowed=\u5DF2\u7ECF\u8D85\u8FC7\u4E86\u5F53\u524D\u4E3B\u4F53({0})\u88AB\u5141\u8BB8\u7684\u6700\u5927\u4F1A\u8BDD\u6570\u91CF
|
||||
@@ -30,14 +30,14 @@ DigestAuthenticationFilter.nonceNotTwoTokens=Nonce\u5E94\u8BE5\u7531\u4E24\u90E8
|
||||
DigestAuthenticationFilter.usernameNotFound=\u7528\u6237\u540D{0}\u672A\u627E\u5230
|
||||
JdbcDaoImpl.noAuthority=\u6CA1\u6709\u4E3A\u7528\u6237{0}\u6307\u5B9A\u89D2\u8272
|
||||
JdbcDaoImpl.notFound=\u672A\u627E\u5230\u7528\u6237{0}
|
||||
LdapAuthenticationProvider.badCredentials=\u574F\u7684\u51ED\u8BC1
|
||||
LdapAuthenticationProvider.badCredentials=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
|
||||
LdapAuthenticationProvider.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
|
||||
LdapAuthenticationProvider.disabled=\u7528\u6237\u5DF2\u5931\u6548
|
||||
LdapAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
|
||||
LdapAuthenticationProvider.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
|
||||
LdapAuthenticationProvider.emptyUsername=\u7528\u6237\u540D\u4E0D\u5141\u8BB8\u4E3A\u7A7A
|
||||
LdapAuthenticationProvider.onlySupports=\u4EC5\u4EC5\u652F\u6301UsernamePasswordAuthenticationToken
|
||||
PasswordComparisonAuthenticator.badCredentials=\u574F\u7684\u51ED\u8BC1
|
||||
PasswordComparisonAuthenticator.badCredentials=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
|
||||
#PersistentTokenBasedRememberMeServices.cookieStolen=Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.
|
||||
ProviderManager.providerNotFound=\u672A\u67E5\u627E\u5230\u9488\u5BF9{0}\u7684AuthenticationProvider
|
||||
RememberMeAuthenticationProvider.incorrectKey=\u5C55\u793ARememberMeAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ public class MethodInvocationFactory {
|
||||
public static MockMethodInvocation createSec2150MethodInvocation()
|
||||
throws NoSuchMethodException {
|
||||
ProxyFactory factory = new ProxyFactory(new Class[] { PersonRepository.class });
|
||||
factory.setTargetClass(CrudRepository.class);
|
||||
factory.setTargetClass(PersonRepositoryImpl.class);
|
||||
PersonRepository repository = (PersonRepository) factory.getProxy();
|
||||
return new MockMethodInvocation(repository, PersonRepository.class, "findAll");
|
||||
}
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.access.annotation.sec2150;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class PersonRepositoryImpl implements PersonRepository {
|
||||
@Override
|
||||
public Iterable<Object> findAll() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.access.expression.method;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
||||
/**
|
||||
* @author shabarijonnalagadda
|
||||
*
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class MethodSecurityEvaluationContextTests {
|
||||
@Mock
|
||||
private ParameterNameDiscoverer paramNameDiscoverer;
|
||||
@Mock
|
||||
private Authentication authentication;
|
||||
@Mock
|
||||
private MethodInvocation methodInvocation;
|
||||
|
||||
@Test
|
||||
public void lookupVariableWhenParameterNameNullThenNotSet() {
|
||||
Class<String> type = String.class;
|
||||
Method method = ReflectionUtils.findMethod(String.class, "contains", CharSequence.class);
|
||||
doReturn(new String[] {null}).when(paramNameDiscoverer).getParameterNames(method);
|
||||
doReturn(new Object[]{null}).when(methodInvocation).getArguments();
|
||||
doReturn(type).when(methodInvocation).getThis();
|
||||
doReturn(method).when(methodInvocation).getMethod();
|
||||
NotNullVariableMethodSecurityEvaluationContext context= new NotNullVariableMethodSecurityEvaluationContext(authentication, methodInvocation, paramNameDiscoverer);
|
||||
context.lookupVariable("testVariable");
|
||||
}
|
||||
|
||||
private static class NotNullVariableMethodSecurityEvaluationContext
|
||||
extends MethodSecurityEvaluationContext {
|
||||
|
||||
public NotNullVariableMethodSecurityEvaluationContext(Authentication auth, MethodInvocation mi,
|
||||
ParameterNameDiscoverer parameterNameDiscoverer) {
|
||||
super(auth, mi, parameterNameDiscoverer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVariable(String name, @Nullable Object value) {
|
||||
if ( name == null ) {
|
||||
throw new IllegalArgumentException("name should not be null");
|
||||
}
|
||||
else {
|
||||
super.setVariable(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
-8
@@ -16,18 +16,20 @@
|
||||
|
||||
package org.springframework.security.authentication;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Tests {@link ProviderManager}.
|
||||
*
|
||||
@@ -257,7 +259,6 @@ public class ProviderManagerTests {
|
||||
catch (BadCredentialsException e) {
|
||||
assertThat(e).isSameAs(expected);
|
||||
}
|
||||
verify(publisher).publishAuthenticationFailure(expected, authReq);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -298,6 +299,32 @@ public class ProviderManagerTests {
|
||||
}
|
||||
}
|
||||
|
||||
// gh-6281
|
||||
@Test
|
||||
public void authenticateWhenFailsInParentAndPublishesThenChildDoesNotPublish() {
|
||||
BadCredentialsException badCredentialsExParent = new BadCredentialsException("Bad Credentials in parent");
|
||||
ProviderManager parentMgr = new ProviderManager(
|
||||
Collections.singletonList(createProviderWhichThrows(badCredentialsExParent)));
|
||||
ProviderManager childMgr = new ProviderManager(Collections.singletonList(createProviderWhichThrows(
|
||||
new BadCredentialsException("Bad Credentials in child"))), parentMgr);
|
||||
|
||||
AuthenticationEventPublisher publisher = mock(AuthenticationEventPublisher.class);
|
||||
parentMgr.setAuthenticationEventPublisher(publisher);
|
||||
childMgr.setAuthenticationEventPublisher(publisher);
|
||||
|
||||
final Authentication authReq = mock(Authentication.class);
|
||||
|
||||
try {
|
||||
childMgr.authenticate(authReq);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (BadCredentialsException e) {
|
||||
assertThat(e).isSameAs(badCredentialsExParent);
|
||||
}
|
||||
verify(publisher).publishAuthenticationFailure(badCredentialsExParent, authReq); // Parent publishes
|
||||
verifyNoMoreInteractions(publisher); // Child should not publish (duplicate event)
|
||||
}
|
||||
|
||||
private AuthenticationProvider createProviderWhichThrows(
|
||||
final AuthenticationException e) {
|
||||
AuthenticationProvider provider = mock(AuthenticationProvider.class);
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.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.asList(new SimpleGrantedAuthority("authority")));
|
||||
|
||||
assertThat(authenticated.isAuthenticated()).isTrue();
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class UserDetailsRepositoryReactiveAuthenticationManagerTests {
|
||||
@Mock
|
||||
private ReactiveUserDetailsService userDetailsService;
|
||||
|
||||
@Mock
|
||||
private PasswordEncoder encoder;
|
||||
|
||||
@Mock
|
||||
private Scheduler scheduler;
|
||||
|
||||
private UserDetails user = User.withUsername("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
|
||||
private UserDetailsRepositoryReactiveAuthenticationManager manager;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.manager = new UserDetailsRepositoryReactiveAuthenticationManager(this.userDetailsService);
|
||||
when(this.scheduler.schedule(any())).thenAnswer(a -> {
|
||||
Runnable r = a.getArgument(0);
|
||||
return Schedulers.immediate().schedule(r);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSchedulerWhenNullThenIllegalArgumentException() {
|
||||
assertThatCode(() -> this.manager.setScheduler(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authentiateWhenCustomSchedulerThenUsed() {
|
||||
when(this.userDetailsService.findByUsername(any())).thenReturn(Mono.just(this.user));
|
||||
when(this.encoder.matches(any(), any())).thenReturn(true);
|
||||
this.manager.setScheduler(this.scheduler);
|
||||
this.manager.setPasswordEncoder(this.encoder);
|
||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
|
||||
this.user, this.user.getPassword());
|
||||
|
||||
Authentication result = this.manager.authenticate(token).block();
|
||||
|
||||
verify(this.scheduler).schedule(any());
|
||||
}
|
||||
}
|
||||
+10
-6
@@ -16,7 +16,12 @@
|
||||
|
||||
package org.springframework.security.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.junit.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
@@ -25,10 +30,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
/**
|
||||
* @author Jitendra Singh
|
||||
@@ -59,6 +61,8 @@ public class SecurityContextMixinTests extends AbstractMixinTests {
|
||||
assertThat(context.getAuthentication().getPrincipal()).isEqualTo("admin");
|
||||
assertThat(context.getAuthentication().getCredentials()).isEqualTo("1234");
|
||||
assertThat(context.getAuthentication().isAuthenticated()).isTrue();
|
||||
assertThat(context.getAuthentication().getAuthorities()).hasSize(1).contains(new SimpleGrantedAuthority("ROLE_USER"));
|
||||
Collection authorities = context.getAuthentication().getAuthorities();
|
||||
assertThat(authorities).hasSize(1);
|
||||
assertThat(authorities).contains(new SimpleGrantedAuthority("ROLE_USER"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ package org.springframework.security.crypto.codec;
|
||||
* @since 3.0
|
||||
* @deprecated Use java.util.Base64
|
||||
*/
|
||||
@Deprecated
|
||||
public final class Base64 {
|
||||
|
||||
/** No options specified. Value is zero. */
|
||||
|
||||
+3
-2
@@ -37,9 +37,10 @@ import java.util.Base64;
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @deprecated Digest based password encoding is not considered secure. Instead use an
|
||||
* adaptive one way funciton like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
|
||||
* password upgrades.
|
||||
* password upgrades. There are no plans to remove this support. It is deprecated to indicate
|
||||
* that this is a legacy implementation and using it is considered insecure.
|
||||
*/
|
||||
@Deprecated
|
||||
public class LdapShaPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
+3
-2
@@ -71,9 +71,10 @@ import java.util.Base64;
|
||||
* @author Rob winch
|
||||
* @since 5.0
|
||||
* @deprecated Digest based password encoding is not considered secure. Instead use an
|
||||
* adaptive one way funciton like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
|
||||
* password upgrades.
|
||||
* password upgrades. There are no plans to remove this support. It is deprecated to indicate
|
||||
* that this is a legacy implementation and using it is considered insecure.
|
||||
*/
|
||||
@Deprecated
|
||||
public class Md4PasswordEncoder implements PasswordEncoder {
|
||||
|
||||
+3
-2
@@ -74,9 +74,10 @@ import java.util.Base64;
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
* @deprecated Digest based password encoding is not considered secure. Instead use an
|
||||
* adaptive one way funciton like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
|
||||
* password upgrades.
|
||||
* password upgrades. There are no plans to remove this support. It is deprecated to indicate
|
||||
* that this is a legacy implementation and using it is considered insecure.
|
||||
*/
|
||||
@Deprecated
|
||||
public class MessageDigestPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
+3
-2
@@ -41,9 +41,10 @@ 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 funciton like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
|
||||
* password upgrades.
|
||||
* password upgrades. There are no plans to remove this support. It is deprecated to indicate
|
||||
* that this is a legacy implementation and using it is considered insecure.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class StandardPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
@@ -2,6 +2,8 @@ apply plugin: 'io.spring.convention.spring-module'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-security-core')
|
||||
compile 'javax.xml.bind:jaxb-api'
|
||||
compile 'org.springframework.data:spring-data-commons'
|
||||
compile 'org.springframework:spring-core'
|
||||
|
||||
}
|
||||
|
||||
@@ -2,24 +2,21 @@ apply plugin: 'org.asciidoctor.gradle.asciidoctor'
|
||||
|
||||
asciidoctor {
|
||||
baseDir = file('src/docs/asciidoc')
|
||||
options = [
|
||||
eruby: 'erubis',
|
||||
attributes: [
|
||||
copycss : '',
|
||||
icons : 'font',
|
||||
'source-highlighter': 'prettify',
|
||||
sectanchors : '',
|
||||
toc: '',
|
||||
'toc-placement' : 'preamble',
|
||||
idprefix: '',
|
||||
idseparator: '-',
|
||||
doctype: 'book',
|
||||
'spring-security-version' : project.version,
|
||||
'download-url' : getDownloadUrl(),
|
||||
'include-maven-repository' : getMavenRepositoryInclude(),
|
||||
revnumber : project.version
|
||||
]
|
||||
]
|
||||
options eruby: 'erubis'
|
||||
|
||||
attributes copycss : '',
|
||||
icons : 'font',
|
||||
'source-highlighter': 'prettify',
|
||||
sectanchors : '',
|
||||
toc: '',
|
||||
'toc-placement' : 'preamble',
|
||||
idprefix: '',
|
||||
idseparator: '-',
|
||||
doctype: 'book',
|
||||
'spring-security-version' : project.version,
|
||||
'download-url' : getDownloadUrl(),
|
||||
'include-maven-repository' : getMavenRepositoryInclude(),
|
||||
revnumber : project.version
|
||||
}
|
||||
|
||||
ext.spec = copySpec {
|
||||
|
||||
@@ -54,6 +54,7 @@ This is a basic example of how to setup Spring Security Test. The highlights are
|
||||
|
||||
NOTE: Spring Security hooks into Spring Test support using the `WithSecurityContextTestExecutionListener` which will ensure our tests are ran with the correct user.
|
||||
It does this by populating the `SecurityContextHolder` prior to running our tests.
|
||||
If you are using reactive method security, you will also need `ReactorContextTestExecutionListener` which populates `ReactiveSecurityContextHolder`.
|
||||
After the test is done, it will clear out the `SecurityContextHolder`.
|
||||
If you only need Spring Security related support, you can replace `@ContextConfiguration` with `@SecurityTestExecutionListeners`.
|
||||
|
||||
|
||||
@@ -4253,7 +4253,7 @@ name: $("meta[name='_csrf_header']").attr("content")
|
||||
----
|
||||
|
||||
|
||||
The configured client can be shared with any component of the application that needs to make a request to the CSRF protected resource. One significant different between rest.js and jQuery is that only requests made with the configured client will contain the CSRF token, vs jQuery where __all__ requests will include the token. The ability to scope which requests receive the token helps guard against leaking the CSRF token to a third party. Please refer to the https://github.com/cujojs/rest/tree/master/docs[rest.js reference documentation] for more information on rest.js.
|
||||
The configured client can be shared with any component of the application that needs to make a request to the CSRF protected resource. One significant difference between rest.js and jQuery is that only requests made with the configured client will contain the CSRF token, vs jQuery where __all__ requests will include the token. The ability to scope which requests receive the token helps guard against leaking the CSRF token to a third party. Please refer to the https://github.com/cujojs/rest/tree/master/docs[rest.js reference documentation] for more information on rest.js.
|
||||
|
||||
[[csrf-cookie]]
|
||||
===== CookieCsrfTokenRepository
|
||||
@@ -8717,7 +8717,7 @@ There are four tables used by the Spring Security <<domain-acls,ACL>> implementa
|
||||
|
||||
. `acl_sid` stores the security identities recognised by the ACL system. These can be unique principals or authorities which may apply to multiple principals.
|
||||
. `acl_class` defines the domain object types to which ACLs apply. The `class` column stores the Java class name of the object.
|
||||
. `acl_object_identity` stores the object identity definitions of specific domai objects.
|
||||
. `acl_object_identity` stores the object identity definitions of specific domain objects.
|
||||
. `acl_entry` stores the ACL permissions which apply to a specific object identity and security identity.
|
||||
|
||||
It is assumed that the database will auto-generate the primary keys for each of the identities. The `JdbcMutableAclService` has to be able to retrieve these when it has created a new row in the `acl_sid` or `acl_class` tables. It has two properties which define the SQL needed to retrieve these values `classIdentityQuery` and `sidIdentityQuery`. Both of these default to `call identity()`
|
||||
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
gaeVersion=1.9.62
|
||||
springBootVersion=2.0.0.RC2
|
||||
version=5.0.4.BUILD-SNAPSHOT
|
||||
gaeVersion=1.9.68
|
||||
springBootVersion=2.0.7.RELEASE
|
||||
version=5.0.11.RELEASE
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom 'io.projectreactor:reactor-bom:Bismuth-SR7'
|
||||
mavenBom 'org.springframework:spring-framework-bom:5.0.4.RELEASE'
|
||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR5'
|
||||
mavenBom 'io.projectreactor:reactor-bom:Bismuth-SR15'
|
||||
mavenBom 'org.springframework:spring-framework-bom:5.0.12.RELEASE'
|
||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR13'
|
||||
}
|
||||
dependencies {
|
||||
dependency 'cglib:cglib-nodep:3.2.6'
|
||||
dependency 'com.squareup.okhttp3:mockwebserver:3.9.1'
|
||||
dependency 'cglib:cglib-nodep:3.2.10'
|
||||
dependency 'com.squareup.okhttp3:mockwebserver:3.12.1'
|
||||
dependency 'opensymphony:sitemesh:2.4.2'
|
||||
dependency 'org.gebish:geb-spock:0.10.0'
|
||||
dependency 'org.jasig.cas:cas-server-webapp:4.2.7'
|
||||
dependency 'org.powermock:powermock-api-mockito2:2.0.0-beta.5'
|
||||
dependency 'org.powermock:powermock-api-support:2.0.0-beta.5'
|
||||
dependency 'org.powermock:powermock-core:2.0.0-beta.5'
|
||||
dependency 'org.powermock:powermock-module-junit4-common:2.0.0-beta.5'
|
||||
dependency 'org.powermock:powermock-module-junit4:2.0.0-beta.5'
|
||||
dependency 'org.powermock:powermock-reflect:2.0.0-beta.5'
|
||||
dependency 'org.powermock:powermock-api-mockito2:2.0.0'
|
||||
dependency 'org.powermock:powermock-api-support:2.0.0'
|
||||
dependency 'org.powermock:powermock-core:2.0.0'
|
||||
dependency 'org.powermock:powermock-module-junit4-common:2.0.0'
|
||||
dependency 'org.powermock:powermock-module-junit4:2.0.0'
|
||||
dependency 'org.powermock:powermock-reflect:2.0.0'
|
||||
dependency 'org.python:jython:2.5.0'
|
||||
dependency 'org.spockframework:spock-core:1.0-groovy-2.4'
|
||||
dependency 'org.spockframework:spock-spring:1.0-groovy-2.4'
|
||||
@@ -28,25 +28,27 @@ dependencyManagement {
|
||||
dependency 'asm:asm:3.1'
|
||||
dependency 'ch.qos.logback:logback-classic:1.2.3'
|
||||
dependency 'ch.qos.logback:logback-core:1.2.3'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-annotations:2.9.4'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-core:2.9.4'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.4'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-annotations:2.9.8'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-core:2.9.8'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
|
||||
dependency 'com.fasterxml:classmate:1.3.4'
|
||||
dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1'
|
||||
dependency 'com.google.appengine:appengine-api-1.0-sdk:1.9.62'
|
||||
dependency 'com.google.appengine:appengine-api-labs:1.9.62'
|
||||
dependency 'com.google.appengine:appengine-api-stubs:1.9.62'
|
||||
dependency 'com.google.appengine:appengine-testing:1.9.62'
|
||||
dependency 'com.google.appengine:appengine:1.9.62'
|
||||
dependency 'com.google.appengine:appengine-api-1.0-sdk:1.9.64'
|
||||
dependency 'com.google.appengine:appengine-api-labs:1.9.64'
|
||||
dependency 'com.google.appengine:appengine-api-stubs:1.9.64'
|
||||
dependency 'com.google.appengine:appengine-testing:1.9.64'
|
||||
dependency 'com.google.appengine:appengine:1.9.63'
|
||||
dependency 'com.google.code.gson:gson:2.8.2'
|
||||
dependency 'com.google.guava:guava:20.0'
|
||||
dependency 'com.google.inject:guice:3.0'
|
||||
dependency 'com.nimbusds:lang-tag:1.4.3'
|
||||
dependency 'com.nimbusds:nimbus-jose-jwt:5.5'
|
||||
dependency 'com.nimbusds:oauth2-oidc-sdk:5.54'
|
||||
dependency 'com.squareup.okhttp3:okhttp:3.9.0'
|
||||
dependency 'com.nimbusds:nimbus-jose-jwt:5.14'
|
||||
dependency 'com.nimbusds:oauth2-oidc-sdk:5.64.4'
|
||||
dependency 'com.squareup.okhttp3:okhttp:3.12.1'
|
||||
dependency 'com.squareup.okio:okio:1.13.0'
|
||||
dependency 'com.unboundid:unboundid-ldapsdk:4.0.4'
|
||||
dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1'
|
||||
dependency 'com.sun.xml.bind:jaxb-impl:2.3.0.1'
|
||||
dependency 'com.unboundid:unboundid-ldapsdk:4.0.9'
|
||||
dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1'
|
||||
dependency 'commons-cli:commons-cli:1.4'
|
||||
dependency 'commons-codec:commons-codec:1.11'
|
||||
@@ -60,10 +62,11 @@ dependencyManagement {
|
||||
dependency 'javax.annotation:jsr250-api:1.0'
|
||||
dependency 'javax.inject:javax.inject:1'
|
||||
dependency 'javax.mail:mail:1.4.7'
|
||||
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1'
|
||||
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.2-b02'
|
||||
dependency 'javax.servlet:javax.servlet-api:4.0.0'
|
||||
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.2'
|
||||
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.3'
|
||||
dependency 'javax.servlet:javax.servlet-api:4.0.1'
|
||||
dependency 'javax.validation:validation-api:2.0.1.Final'
|
||||
dependency 'javax.xml.bind:jaxb-api:2.3.0'
|
||||
dependency 'junit:junit:4.12'
|
||||
dependency 'ldapsdk:ldapsdk:4.1'
|
||||
dependency 'net.bytebuddy:byte-buddy-agent:1.7.9'
|
||||
@@ -71,14 +74,12 @@ dependencyManagement {
|
||||
dependency 'net.jcip:jcip-annotations:1.0'
|
||||
dependency 'net.minidev:accessors-smart:1.2'
|
||||
dependency 'net.minidev:json-smart:2.3'
|
||||
dependency 'net.sf.ehcache:ehcache:2.10.4'
|
||||
dependency 'net.sourceforge.cssparser:cssparser:0.9.24'
|
||||
dependency 'net.sourceforge.htmlunit:htmlunit-core-js:2.28'
|
||||
dependency 'net.sourceforge.htmlunit:htmlunit:2.29'
|
||||
dependency 'net.sourceforge.htmlunit:neko-htmlunit:2.28'
|
||||
dependency 'net.sf.ehcache:ehcache:2.10.6'
|
||||
dependency 'net.sourceforge.htmlunit:htmlunit:2.33'
|
||||
dependency 'net.sourceforge.htmlunit:neko-htmlunit:2.32'
|
||||
dependency 'net.sourceforge.nekohtml:nekohtml:1.9.22'
|
||||
dependency 'nz.net.ultraq.thymeleaf:thymeleaf-expression-processor:1.1.3'
|
||||
dependency 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:2.2.2'
|
||||
dependency 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:2.3.0'
|
||||
dependency 'ognl:ognl:3.1.12'
|
||||
dependency 'org.akhikhl.gretty:gretty-runner-tomcat8:2.0.0'
|
||||
dependency 'org.akhikhl.gretty:gretty-runner-tomcat:2.0.0'
|
||||
@@ -114,7 +115,7 @@ dependencyManagement {
|
||||
dependency 'org.apache.directory.shared:shared-cursor:0.9.15'
|
||||
dependency 'org.apache.directory.shared:shared-ldap-constants:0.9.15'
|
||||
dependency 'org.apache.directory.shared:shared-ldap:0.9.15'
|
||||
dependency 'org.apache.httpcomponents:httpclient:4.5.5'
|
||||
dependency 'org.apache.httpcomponents:httpclient:4.5.6'
|
||||
dependency 'org.apache.httpcomponents:httpcore:4.4.8'
|
||||
dependency 'org.apache.httpcomponents:httpmime:4.5.3'
|
||||
dependency 'org.apache.mina:mina-core:2.0.0-M6'
|
||||
@@ -127,16 +128,16 @@ dependencyManagement {
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:8.0.44'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.23'
|
||||
dependency 'org.apache.tomcat:tomcat-annotations-api:8.5.23'
|
||||
dependency 'org.aspectj:aspectjrt:1.8.13'
|
||||
dependency 'org.aspectj:aspectjtools:1.8.13'
|
||||
dependency 'org.aspectj:aspectjweaver:1.8.13'
|
||||
dependency 'org.assertj:assertj-core:3.9.0'
|
||||
dependency 'org.aspectj:aspectjrt:1.9.2'
|
||||
dependency 'org.aspectj:aspectjtools:1.9.2'
|
||||
dependency 'org.aspectj:aspectjweaver:1.9.2'
|
||||
dependency 'org.assertj:assertj-core:3.11.1'
|
||||
dependency 'org.attoparser:attoparser:2.0.4.RELEASE'
|
||||
dependency 'org.bouncycastle:bcpkix-jdk15on:1.59'
|
||||
dependency 'org.bouncycastle:bcpkix-jdk15on:1.60'
|
||||
dependency 'org.bouncycastle:bcprov-jdk15on:1.58'
|
||||
dependency 'org.codehaus.groovy:groovy-all:2.5.0-beta-1'
|
||||
dependency 'org.codehaus.groovy:groovy-json:2.5.0-beta-1'
|
||||
dependency 'org.codehaus.groovy:groovy:2.5.0-beta-1'
|
||||
dependency 'org.codehaus.groovy:groovy-all:2.4.14'
|
||||
dependency 'org.codehaus.groovy:groovy-json:2.4.14'
|
||||
dependency 'org.codehaus.groovy:groovy:2.4.14'
|
||||
dependency 'org.eclipse.jdt:ecj:3.12.3'
|
||||
dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.7.v20170914'
|
||||
dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.7.v20170914'
|
||||
@@ -149,7 +150,7 @@ dependencyManagement {
|
||||
dependency 'org.eclipse.jetty:jetty-servlet:9.4.7.v20170914'
|
||||
dependency 'org.eclipse.jetty:jetty-util:9.4.7.v20170914'
|
||||
dependency 'org.eclipse.jetty:jetty-xml:9.4.7.v20170914'
|
||||
dependency 'org.eclipse.persistence:javax.persistence:2.2.0'
|
||||
dependency 'org.eclipse.persistence:javax.persistence:2.2.1'
|
||||
dependency 'org.gebish:geb-ast:0.10.0'
|
||||
dependency 'org.gebish:geb-core:0.10.0'
|
||||
dependency 'org.gebish:geb-implicit-assertions:0.10.0'
|
||||
@@ -157,24 +158,23 @@ dependencyManagement {
|
||||
dependency 'org.hamcrest:hamcrest-core:1.3'
|
||||
dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final'
|
||||
dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final'
|
||||
dependency 'org.hibernate:hibernate-core:5.2.12.Final'
|
||||
dependency 'org.hibernate:hibernate-entitymanager:5.2.12.Final'
|
||||
dependency 'org.hibernate:hibernate-validator:6.0.5.Final'
|
||||
dependency 'org.hsqldb:hsqldb:2.4.0'
|
||||
dependency 'org.hibernate:hibernate-core:5.2.17.Final'
|
||||
dependency 'org.hibernate:hibernate-entitymanager:5.2.17.Final'
|
||||
dependency 'org.hibernate:hibernate-validator:6.0.12.Final'
|
||||
dependency 'org.hsqldb:hsqldb:2.4.1'
|
||||
dependency 'org.jasig.cas.client:cas-client-core:3.5.0'
|
||||
dependency 'org.javassist:javassist:3.22.0-CR2'
|
||||
dependency 'org.jboss.logging:jboss-logging:3.3.1.Final'
|
||||
dependency 'org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.0.1.Final'
|
||||
dependency 'org.jboss:jandex:2.0.3.Final'
|
||||
dependency 'org.mockito:mockito-core:2.13.0'
|
||||
dependency 'org.mockito:mockito-core:2.18.3'
|
||||
dependency 'org.objenesis:objenesis:2.6'
|
||||
dependency 'org.openid4java:openid4java-nodeps:0.9.6'
|
||||
dependency 'org.ow2.asm:asm:5.0.4'
|
||||
dependency 'org.ow2.asm:asm:6.0'
|
||||
dependency 'org.reactivestreams:reactive-streams:1.0.1'
|
||||
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.29.2'
|
||||
dependency 'org.seleniumhq.selenium:selenium-api:3.8.1'
|
||||
dependency 'org.seleniumhq.selenium:selenium-java:3.9.1'
|
||||
dependency 'org.seleniumhq.selenium:selenium-support:3.9.1'
|
||||
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.33.0'
|
||||
dependency 'org.seleniumhq.selenium:selenium-java:3.14.0'
|
||||
dependency 'org.seleniumhq.selenium:selenium-support:3.14.0'
|
||||
dependency 'org.skyscreamer:jsonassert:1.5.0'
|
||||
dependency 'org.slf4j:jcl-over-slf4j:1.7.25'
|
||||
dependency 'org.slf4j:jul-to-slf4j:1.7.25'
|
||||
@@ -183,8 +183,7 @@ dependencyManagement {
|
||||
dependency 'org.slf4j:slf4j-nop:1.7.25'
|
||||
dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111'
|
||||
dependency 'org.springframework.ldap:spring-ldap-core:2.3.2.RELEASE'
|
||||
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.9.RELEASE'
|
||||
dependency 'org.thymeleaf:thymeleaf:3.0.9.RELEASE'
|
||||
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE'
|
||||
dependency 'org.unbescape:unbescape:1.1.5.RELEASE'
|
||||
dependency 'org.w3c.css:sac:1.3'
|
||||
dependency 'xalan:serializer:2.7.2'
|
||||
|
||||
+1
-1
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip
|
||||
|
||||
@@ -20,12 +20,15 @@ dependencies {
|
||||
exclude(group: 'org.springframework.data', module: 'spring-data-commons')
|
||||
}
|
||||
|
||||
testCompile project(':spring-security-test')
|
||||
testCompile 'org.slf4j:jcl-over-slf4j'
|
||||
testCompile 'org.slf4j:slf4j-api'
|
||||
}
|
||||
|
||||
integrationTest {
|
||||
include('**/ApacheDSServerIntegrationTests.class', '**/ApacheDSEmbeddedLdifTests.class')
|
||||
include('**/ApacheDSServerIntegrationTests.class',
|
||||
'**/ApacheDSEmbeddedLdifTests.class',
|
||||
'**/LdapUserDetailsManagerModifyPasswordTests.class')
|
||||
// exclude('**/OpenLDAPIntegrationTestSuite.class')
|
||||
maxParallelForks = 1
|
||||
}
|
||||
|
||||
+21
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* 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.
|
||||
@@ -22,8 +22,9 @@ import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* @author Eddú Meléndez
|
||||
@@ -31,17 +32,31 @@ import static org.junit.Assert.fail;
|
||||
public class UnboundIdContainerTests {
|
||||
|
||||
@Test
|
||||
public void startLdapServer() throws IOException {
|
||||
public void startLdapServer() throws Exception {
|
||||
UnboundIdContainer server = new UnboundIdContainer("dc=springframework,dc=org",
|
||||
"classpath:test-server.ldif");
|
||||
server.setApplicationContext(new GenericApplicationContext());
|
||||
List<Integer> ports = getDefaultPorts(1);
|
||||
server.setPort(ports.get(0));
|
||||
|
||||
try {
|
||||
server.afterPropertiesSet();
|
||||
fail("Expected a RuntimeException to be thrown.");
|
||||
} catch (Exception ex) {
|
||||
assertThat(ex).hasMessage("Server startup failed");
|
||||
assertThat(server.getPort()).isEqualTo(ports.get(0));
|
||||
} finally {
|
||||
server.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void afterPropertiesSetWhenPortIsZeroThenRandomPortIsSelected() throws Exception {
|
||||
UnboundIdContainer server = new UnboundIdContainer("dc=springframework,dc=org", null);
|
||||
server.setPort(0);
|
||||
|
||||
try {
|
||||
server.afterPropertiesSet();
|
||||
assertThat(server.getPort()).isNotEqualTo(0);
|
||||
} finally {
|
||||
server.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.ldap.userdetails;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper;
|
||||
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
|
||||
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
||||
import org.springframework.security.ldap.server.UnboundIdContainer;
|
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
|
||||
/**
|
||||
* Tests for {@link LdapUserDetailsManager#changePassword}, specifically relating to the
|
||||
* use of the Modify Password Extended Operation.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SecurityTestExecutionListeners
|
||||
public class LdapUserDetailsManagerModifyPasswordTests {
|
||||
|
||||
ConfigurableApplicationContext context;
|
||||
|
||||
LdapUserDetailsManager userDetailsManager;
|
||||
ContextSource contextSource;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.context = new AnnotationConfigApplicationContext(ContainerConfiguration.class, LdapConfiguration.class);
|
||||
this.contextSource = this.context.getBean(ContextSource.class);
|
||||
|
||||
this.userDetailsManager = new LdapUserDetailsManager(this.contextSource);
|
||||
this.userDetailsManager.setUsePasswordModifyExtensionOperation(true);
|
||||
this.userDetailsManager.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=people", "uid"));
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
this.context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username="bob", password="bobspassword", authorities="ROLE_USER")
|
||||
public void changePasswordWhenOldPasswordIsIncorrectThenThrowsException() {
|
||||
assertThatCode(() ->
|
||||
this.userDetailsManager.changePassword("wrongoldpassword", "bobsnewpassword"))
|
||||
.isInstanceOf(BadCredentialsException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username="bob", password="bobspassword", authorities="ROLE_USER")
|
||||
public void changePasswordWhenOldPasswordIsCorrectThenPasses() {
|
||||
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(this.contextSource);
|
||||
|
||||
this.userDetailsManager.changePassword("bobspassword",
|
||||
"bobsshinynewandformidablylongandnearlyimpossibletorememberthoughdemonstrablyhardtocrackduetoitshighlevelofentropypasswordofjustice");
|
||||
|
||||
assertThat(template.compare("uid=bob,ou=people", "userPassword",
|
||||
"bobsshinynewandformidablylongandnearlyimpossibletorememberthoughdemonstrablyhardtocrackduetoitshighlevelofentropypasswordofjustice")).isTrue();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class LdapConfiguration {
|
||||
@Autowired UnboundIdContainer container;
|
||||
|
||||
@Bean
|
||||
ContextSource contextSource() throws Exception {
|
||||
return new DefaultSpringSecurityContextSource("ldap://127.0.0.1:"
|
||||
+ this.container.getPort() + "/dc=springframework,dc=org");
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ContainerConfiguration {
|
||||
UnboundIdContainer container = new UnboundIdContainer("dc=springframework,dc=org",
|
||||
"classpath:test-server.ldif");
|
||||
|
||||
@Bean
|
||||
UnboundIdContainer ldapContainer() {
|
||||
this.container.setPort(0);
|
||||
return this.container;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
void shutdown() {
|
||||
this.container.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -42,6 +42,7 @@ import javax.naming.OperationNotSupportedException;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.SearchControls;
|
||||
import javax.naming.ldap.InitialLdapContext;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -219,6 +220,8 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
|
||||
logger.debug("Authentication for " + bindPrincipal + " failed:" + exception);
|
||||
}
|
||||
|
||||
handleResolveObj(exception);
|
||||
|
||||
int subErrorCode = parseSubErrorCode(exception.getMessage());
|
||||
|
||||
if (subErrorCode <= 0) {
|
||||
@@ -234,6 +237,14 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
|
||||
}
|
||||
}
|
||||
|
||||
private void handleResolveObj(NamingException exception) {
|
||||
Object resolvedObj = exception.getResolvedObj();
|
||||
boolean serializable = resolvedObj instanceof Serializable;
|
||||
if (resolvedObj != null && !serializable) {
|
||||
exception.setResolvedObj(null);
|
||||
}
|
||||
}
|
||||
|
||||
private int parseSubErrorCode(String message) {
|
||||
Matcher m = SUB_ERROR_CODE.matcher(message);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* 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.
|
||||
@@ -103,6 +103,7 @@ public class UnboundIdContainer implements InitializingBean, DisposableBean, Lif
|
||||
directoryServer.add(entry);
|
||||
importLdif(directoryServer);
|
||||
directoryServer.startListening();
|
||||
this.port = directoryServer.getListenPort();
|
||||
this.directoryServer = directoryServer;
|
||||
this.running = true;
|
||||
} catch (LDAPException ex) {
|
||||
|
||||
+198
-33
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
* 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.
|
||||
@@ -15,12 +15,13 @@
|
||||
*/
|
||||
package org.springframework.security.ldap.userdetails;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.NameNotFoundException;
|
||||
import javax.naming.NamingEnumeration;
|
||||
@@ -32,10 +33,13 @@ import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.ModificationItem;
|
||||
import javax.naming.directory.SearchControls;
|
||||
import javax.naming.directory.SearchResult;
|
||||
import javax.naming.ldap.ExtendedRequest;
|
||||
import javax.naming.ldap.ExtendedResponse;
|
||||
import javax.naming.ldap.LdapContext;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.ldap.core.AttributesMapper;
|
||||
import org.springframework.ldap.core.AttributesMapperCallbackHandler;
|
||||
import org.springframework.ldap.core.ContextExecutor;
|
||||
@@ -70,6 +74,7 @@ import org.springframework.util.Assert;
|
||||
* setup.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @author Josh Cummings
|
||||
* @since 2.0
|
||||
*/
|
||||
public class LdapUserDetailsManager implements UserDetailsManager {
|
||||
@@ -123,6 +128,8 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
||||
|
||||
private String[] attributesToRetrieve;
|
||||
|
||||
private boolean usePasswordModifyExtensionOperation = false;
|
||||
|
||||
public LdapUserDetailsManager(ContextSource contextSource) {
|
||||
template = new LdapTemplate(contextSource);
|
||||
}
|
||||
@@ -157,8 +164,21 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
||||
/**
|
||||
* Changes the password for the current user. The username is obtained from the
|
||||
* security context.
|
||||
*
|
||||
* There are two supported strategies for modifying the user's password depending on
|
||||
* the capabilities of the corresponding LDAP server.
|
||||
*
|
||||
* <p>
|
||||
* If the old password is supplied, the update will be made by rebinding as the user,
|
||||
* Configured one way, this method will modify the user's password via the
|
||||
* <a target="_blank" href="https://tools.ietf.org/html/rfc3062">
|
||||
* LDAP Password Modify Extended Operation
|
||||
* </a>.
|
||||
*
|
||||
* See {@link LdapUserDetailsManager#setUsePasswordModifyExtensionOperation(boolean)} for details.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* By default, though, if the old password is supplied, the update will be made by rebinding as the user,
|
||||
* thus modifying the password using the user's permissions. If
|
||||
* <code>oldPassword</code> is null, the update will be attempted using a standard
|
||||
* read/write context supplied by the context source.
|
||||
@@ -178,38 +198,13 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
||||
|
||||
logger.debug("Changing password for user '" + username);
|
||||
|
||||
final DistinguishedName dn = usernameMapper.buildDn(username);
|
||||
final ModificationItem[] passwordChange = new ModificationItem[] { new ModificationItem(
|
||||
DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(passwordAttributeName,
|
||||
newPassword)) };
|
||||
DistinguishedName userDn = usernameMapper.buildDn(username);
|
||||
|
||||
if (oldPassword == null) {
|
||||
template.modifyAttributes(dn, passwordChange);
|
||||
return;
|
||||
if (usePasswordModifyExtensionOperation) {
|
||||
changePasswordUsingExtensionOperation(userDn, oldPassword, newPassword);
|
||||
} else {
|
||||
changePasswordUsingAttributeModification(userDn, oldPassword, newPassword);
|
||||
}
|
||||
|
||||
template.executeReadWrite(new ContextExecutor() {
|
||||
|
||||
public Object executeWithContext(DirContext dirCtx) throws NamingException {
|
||||
LdapContext ctx = (LdapContext) dirCtx;
|
||||
ctx.removeFromEnvironment("com.sun.jndi.ldap.connect.pool");
|
||||
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL,
|
||||
LdapUtils.getFullDn(dn, ctx).toString());
|
||||
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, oldPassword);
|
||||
// TODO: reconnect doesn't appear to actually change the credentials
|
||||
try {
|
||||
ctx.reconnect(null);
|
||||
}
|
||||
catch (javax.naming.AuthenticationException e) {
|
||||
throw new BadCredentialsException(
|
||||
"Authentication for password change failed.");
|
||||
}
|
||||
|
||||
ctx.modifyAttributes(dn, passwordChange);
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -414,4 +409,174 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
||||
public void setRoleMapper(AttributesMapper roleMapper) {
|
||||
this.roleMapper = roleMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the method by which a user's password gets modified.
|
||||
*
|
||||
* If set to {@code true}, then {@link LdapUserDetailsManager#changePassword} will modify
|
||||
* the user's password by way of the
|
||||
* <a target="_blank" href="https://tools.ietf.org/html/rfc3062">Password Modify Extension Operation</a>.
|
||||
*
|
||||
* If set to {@code false}, then {@link LdapUserDetailsManager#changePassword} will modify
|
||||
* the user's password by directly modifying attributes on the corresponding entry.
|
||||
*
|
||||
* Before using this setting, ensure that the corresponding LDAP server supports this extended operation.
|
||||
*
|
||||
* By default, {@code usePasswordModifyExtensionOperation} is false.
|
||||
*
|
||||
* @param usePasswordModifyExtensionOperation
|
||||
* @since 4.2.9
|
||||
*/
|
||||
public void setUsePasswordModifyExtensionOperation(boolean usePasswordModifyExtensionOperation) {
|
||||
this.usePasswordModifyExtensionOperation = usePasswordModifyExtensionOperation;
|
||||
}
|
||||
|
||||
private void changePasswordUsingAttributeModification
|
||||
(DistinguishedName userDn, String oldPassword, String newPassword) {
|
||||
|
||||
final ModificationItem[] passwordChange = new ModificationItem[] { new ModificationItem(
|
||||
DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(passwordAttributeName,
|
||||
newPassword)) };
|
||||
|
||||
if (oldPassword == null) {
|
||||
template.modifyAttributes(userDn, passwordChange);
|
||||
return;
|
||||
}
|
||||
|
||||
template.executeReadWrite(dirCtx -> {
|
||||
LdapContext ctx = (LdapContext) dirCtx;
|
||||
ctx.removeFromEnvironment("com.sun.jndi.ldap.connect.pool");
|
||||
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL,
|
||||
LdapUtils.getFullDn(userDn, ctx).toString());
|
||||
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, oldPassword);
|
||||
// TODO: reconnect doesn't appear to actually change the credentials
|
||||
try {
|
||||
ctx.reconnect(null);
|
||||
} catch (javax.naming.AuthenticationException e) {
|
||||
throw new BadCredentialsException(
|
||||
"Authentication for password change failed.");
|
||||
}
|
||||
|
||||
ctx.modifyAttributes(userDn, passwordChange);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void changePasswordUsingExtensionOperation
|
||||
(DistinguishedName userDn, String oldPassword, String newPassword) {
|
||||
|
||||
template.executeReadWrite(dirCtx -> {
|
||||
LdapContext ctx = (LdapContext) dirCtx;
|
||||
|
||||
String userIdentity = LdapUtils.getFullDn(userDn, ctx).encode();
|
||||
PasswordModifyRequest request =
|
||||
new PasswordModifyRequest(userIdentity, oldPassword, newPassword);
|
||||
|
||||
try {
|
||||
return ctx.extendedOperation(request);
|
||||
} catch (javax.naming.AuthenticationException e) {
|
||||
throw new BadCredentialsException(
|
||||
"Authentication for password change failed.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of the
|
||||
* <a target="_blank" href="https://tools.ietf.org/html/rfc3062">
|
||||
* LDAP Password Modify Extended Operation
|
||||
* </a>
|
||||
* client request.
|
||||
*
|
||||
* Can be directed at any LDAP server that supports the Password Modify Extended Operation.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 4.2.9
|
||||
*/
|
||||
private static class PasswordModifyRequest implements ExtendedRequest {
|
||||
private static final byte SEQUENCE_TYPE = 48;
|
||||
|
||||
private static final String PASSWORD_MODIFY_OID = "1.3.6.1.4.1.4203.1.11.1";
|
||||
private static final byte USER_IDENTITY_OCTET_TYPE = -128;
|
||||
private static final byte OLD_PASSWORD_OCTET_TYPE = -127;
|
||||
private static final byte NEW_PASSWORD_OCTET_TYPE = -126;
|
||||
|
||||
private final ByteArrayOutputStream value = new ByteArrayOutputStream();
|
||||
|
||||
public PasswordModifyRequest(String userIdentity, String oldPassword, String newPassword) {
|
||||
ByteArrayOutputStream elements = new ByteArrayOutputStream();
|
||||
|
||||
if (userIdentity != null) {
|
||||
berEncode(USER_IDENTITY_OCTET_TYPE, userIdentity.getBytes(), elements);
|
||||
}
|
||||
|
||||
if (oldPassword != null) {
|
||||
berEncode(OLD_PASSWORD_OCTET_TYPE, oldPassword.getBytes(), elements);
|
||||
}
|
||||
|
||||
if (newPassword != null) {
|
||||
berEncode(NEW_PASSWORD_OCTET_TYPE, newPassword.getBytes(), elements);
|
||||
}
|
||||
|
||||
berEncode(SEQUENCE_TYPE, elements.toByteArray(), this.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getID() {
|
||||
return PASSWORD_MODIFY_OID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncodedValue() {
|
||||
return this.value.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedResponse createExtendedResponse(String id, byte[] berValue, int offset, int length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only minimal support for
|
||||
* <a target="_blank" href="https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf">
|
||||
* BER encoding
|
||||
* </a>; just what is necessary for the Password Modify request.
|
||||
*
|
||||
*/
|
||||
private void berEncode(byte type, byte[] src, ByteArrayOutputStream dest) {
|
||||
int length = src.length;
|
||||
|
||||
dest.write(type);
|
||||
|
||||
if (length < 128) {
|
||||
dest.write(length);
|
||||
} else if ((length & 0x0000_00FF) == length) {
|
||||
dest.write((byte) 0x81);
|
||||
dest.write((byte) (length & 0xFF));
|
||||
} else if ((length & 0x0000_FFFF) == length) {
|
||||
dest.write((byte) 0x82);
|
||||
dest.write((byte) ((length >> 8) & 0xFF));
|
||||
dest.write((byte) (length & 0xFF));
|
||||
} else if ((length & 0x00FF_FFFF) == length) {
|
||||
dest.write((byte) 0x83);
|
||||
dest.write((byte) ((length >> 16) & 0xFF));
|
||||
dest.write((byte) ((length >> 8) & 0xFF));
|
||||
dest.write((byte) (length & 0xFF));
|
||||
} else {
|
||||
dest.write((byte) 0x84);
|
||||
dest.write((byte) ((length >> 24) & 0xFF));
|
||||
dest.write((byte) ((length >> 16) & 0xFF));
|
||||
dest.write((byte) ((length >> 8) & 0xFF));
|
||||
dest.write((byte) (length & 0xFF));
|
||||
}
|
||||
|
||||
try {
|
||||
dest.write(src);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Failed to BER encode provided value of type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* 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.
|
||||
@@ -24,6 +24,7 @@ import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
|
||||
import com.nimbusds.openid.connect.sdk.UserInfoRequest;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.AbstractClientHttpResponse;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
@@ -84,6 +85,7 @@ final class NimbusUserInfoResponseClient {
|
||||
|
||||
UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, accessToken);
|
||||
HTTPRequest httpRequest = userInfoRequest.toHTTPRequest();
|
||||
httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE);
|
||||
httpRequest.setConnectTimeout(30000);
|
||||
httpRequest.setReadTimeout(30000);
|
||||
HTTPResponse httpResponse;
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* 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.
|
||||
@@ -24,6 +24,7 @@ import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
|
||||
import com.nimbusds.openid.connect.sdk.UserInfoRequest;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.AbstractClientHttpResponse;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
@@ -81,6 +82,7 @@ final class NimbusUserInfoResponseClient {
|
||||
|
||||
UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, accessToken);
|
||||
HTTPRequest httpRequest = userInfoRequest.toHTTPRequest();
|
||||
httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE);
|
||||
httpRequest.setConnectTimeout(30000);
|
||||
httpRequest.setReadTimeout(30000);
|
||||
HTTPResponse httpResponse;
|
||||
|
||||
+5
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* 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.
|
||||
@@ -25,6 +25,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||
import org.springframework.security.web.RedirectStrategy;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
@@ -183,16 +184,9 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
|
||||
}
|
||||
|
||||
private String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration) {
|
||||
int port = request.getServerPort();
|
||||
if (("http".equals(request.getScheme()) && port == 80) || ("https".equals(request.getScheme()) && port == 443)) {
|
||||
port = -1; // Removes the port in UriComponentsBuilder
|
||||
}
|
||||
|
||||
String baseUrl = UriComponentsBuilder.newInstance()
|
||||
.scheme(request.getScheme())
|
||||
.host(request.getServerName())
|
||||
.port(port)
|
||||
.path(request.getContextPath())
|
||||
String baseUrl = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
||||
.replaceQuery(null)
|
||||
.replacePath(request.getContextPath())
|
||||
.build()
|
||||
.toUriString();
|
||||
|
||||
|
||||
+12
-2
@@ -34,8 +34,10 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.context.SecurityContextRepository;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -97,6 +99,7 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce
|
||||
*/
|
||||
public static final String DEFAULT_FILTER_PROCESSES_URI = "/login/oauth2/code/*";
|
||||
private static final String AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE = "authorization_request_not_found";
|
||||
private static final String CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE = "client_registration_not_found";
|
||||
private ClientRegistrationRepository clientRegistrationRepository;
|
||||
private OAuth2AuthorizedClientService authorizedClientService;
|
||||
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
|
||||
@@ -148,7 +151,11 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce
|
||||
|
||||
String registrationId = (String) authorizationRequest.getAdditionalParameters().get(OAuth2ParameterNames.REGISTRATION_ID);
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
|
||||
|
||||
if (clientRegistration == null) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE,
|
||||
"Client Registration not found with Id: " + registrationId, null);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
OAuth2AuthorizationResponse authorizationResponse = this.convert(request);
|
||||
|
||||
OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(
|
||||
@@ -187,7 +194,10 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce
|
||||
String code = request.getParameter(OAuth2ParameterNames.CODE);
|
||||
String errorCode = request.getParameter(OAuth2ParameterNames.ERROR);
|
||||
String state = request.getParameter(OAuth2ParameterNames.STATE);
|
||||
String redirectUri = request.getRequestURL().toString();
|
||||
String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
||||
.replaceQuery(null)
|
||||
.build()
|
||||
.toUriString();
|
||||
|
||||
if (StringUtils.hasText(code)) {
|
||||
return OAuth2AuthorizationResponse.success(code)
|
||||
|
||||
+32
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* 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.
|
||||
@@ -44,6 +44,7 @@ import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
@@ -290,4 +291,34 @@ public class OidcUserServiceTests {
|
||||
|
||||
assertThat(user.getName()).isEqualTo("user1@example.com");
|
||||
}
|
||||
|
||||
// gh-5294
|
||||
@Test
|
||||
public void loadUserWhenUserInfoSuccessResponseThenAcceptHeaderJson() throws Exception {
|
||||
MockWebServer server = new MockWebServer();
|
||||
|
||||
String userInfoResponse = "{\n" +
|
||||
" \"sub\": \"subject1\",\n" +
|
||||
" \"name\": \"first last\",\n" +
|
||||
" \"given_name\": \"first\",\n" +
|
||||
" \"family_name\": \"last\",\n" +
|
||||
" \"preferred_username\": \"user1\",\n" +
|
||||
" \"email\": \"user1@example.com\"\n" +
|
||||
"}\n";
|
||||
server.enqueue(new MockResponse()
|
||||
.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
||||
.setBody(userInfoResponse));
|
||||
|
||||
server.start();
|
||||
|
||||
String userInfoUri = server.url("/user").toString();
|
||||
|
||||
when(this.userInfoEndpoint.getUri()).thenReturn(userInfoUri);
|
||||
when(this.accessToken.getTokenValue()).thenReturn("access-token");
|
||||
|
||||
this.userService.loadUser(new OidcUserRequest(this.clientRegistration, this.accessToken, this.idToken));
|
||||
server.shutdown();
|
||||
assertThat(server.takeRequest(1, TimeUnit.SECONDS).getHeader(HttpHeaders.ACCEPT))
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
+34
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* 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.
|
||||
@@ -34,6 +34,8 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -204,4 +206,35 @@ public class DefaultOAuth2UserServiceTests {
|
||||
|
||||
this.userService.loadUser(new OAuth2UserRequest(this.clientRegistration, this.accessToken));
|
||||
}
|
||||
|
||||
// gh-5294
|
||||
@Test
|
||||
public void loadUserWhenUserInfoSuccessResponseThenAcceptHeaderJson() throws Exception {
|
||||
MockWebServer server = new MockWebServer();
|
||||
|
||||
String userInfoResponse = "{\n" +
|
||||
" \"user-name\": \"user1\",\n" +
|
||||
" \"first-name\": \"first\",\n" +
|
||||
" \"last-name\": \"last\",\n" +
|
||||
" \"middle-name\": \"middle\",\n" +
|
||||
" \"address\": \"address\",\n" +
|
||||
" \"email\": \"user1@example.com\"\n" +
|
||||
"}\n";
|
||||
server.enqueue(new MockResponse()
|
||||
.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
||||
.setBody(userInfoResponse));
|
||||
|
||||
server.start();
|
||||
|
||||
String userInfoUri = server.url("/user").toString();
|
||||
|
||||
when(this.userInfoEndpoint.getUri()).thenReturn(userInfoUri);
|
||||
when(this.userInfoEndpoint.getUserNameAttributeName()).thenReturn("user-name");
|
||||
when(this.accessToken.getTokenValue()).thenReturn("access-token");
|
||||
|
||||
this.userService.loadUser(new OAuth2UserRequest(this.clientRegistration, this.accessToken));
|
||||
server.shutdown();
|
||||
assertThat(server.takeRequest(1, TimeUnit.SECONDS).getHeader(HttpHeaders.ACCEPT))
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
+162
-3
@@ -43,9 +43,12 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -64,7 +67,7 @@ import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
@PowerMockIgnore("javax.security.*")
|
||||
@PrepareForTest({OAuth2AuthorizationRequest.class, OAuth2AuthorizationExchange.class, OAuth2LoginAuthenticationFilter.class})
|
||||
@PrepareForTest({OAuth2AuthorizationExchange.class, OAuth2LoginAuthenticationFilter.class})
|
||||
@RunWith(PowerMockRunner.class)
|
||||
public class OAuth2LoginAuthenticationFilterTests {
|
||||
private ClientRegistration registration1;
|
||||
@@ -197,6 +200,44 @@ public class OAuth2LoginAuthenticationFilterTests {
|
||||
assertThat(authenticationException.getError().getErrorCode()).isEqualTo("authorization_request_not_found");
|
||||
}
|
||||
|
||||
// gh-5251
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationResponseClientRegistrationNotFoundThenClientRegistrationNotFoundError() throws Exception {
|
||||
String requestUri = "/login/oauth2/code/" + this.registration2.getRegistrationId();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
ClientRegistration registrationNotFound = ClientRegistration.withRegistrationId("registration-not-found")
|
||||
.clientId("client-1")
|
||||
.clientSecret("secret")
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
|
||||
.scope("user")
|
||||
.authorizationUri("https://provider.com/oauth2/authorize")
|
||||
.tokenUri("https://provider.com/oauth2/token")
|
||||
.userInfoUri("https://provider.com/oauth2/user")
|
||||
.userNameAttributeName("id")
|
||||
.clientName("client-1")
|
||||
.build();
|
||||
this.setUpAuthorizationRequest(request, response, registrationNotFound);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
ArgumentCaptor<AuthenticationException> authenticationExceptionArgCaptor = ArgumentCaptor.forClass(AuthenticationException.class);
|
||||
verify(this.failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
authenticationExceptionArgCaptor.capture());
|
||||
|
||||
assertThat(authenticationExceptionArgCaptor.getValue()).isInstanceOf(OAuth2AuthenticationException.class);
|
||||
OAuth2AuthenticationException authenticationException = (OAuth2AuthenticationException) authenticationExceptionArgCaptor.getValue();
|
||||
assertThat(authenticationException.getError().getErrorCode()).isEqualTo("client_registration_not_found");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationResponseValidThenAuthorizationRequestRemoved() throws Exception {
|
||||
String requestUri = "/login/oauth2/code/" + this.registration2.getRegistrationId();
|
||||
@@ -284,15 +325,133 @@ public class OAuth2LoginAuthenticationFilterTests {
|
||||
}
|
||||
}
|
||||
|
||||
// gh-5756
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationResponseHasDefaultPort80ThenRedirectUriMatchingExcludesPort() throws Exception {
|
||||
String requestUri = "/login/oauth2/code/" + this.registration2.getRegistrationId();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setScheme("http");
|
||||
request.setServerName("example.com");
|
||||
request.setServerPort(80);
|
||||
request.setServletPath(requestUri);
|
||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.setUpAuthorizationRequest(request, response, this.registration2);
|
||||
this.setUpAuthenticationResult(this.registration2);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
ArgumentCaptor<Authentication> authenticationArgCaptor = ArgumentCaptor.forClass(Authentication.class);
|
||||
verify(this.authenticationManager).authenticate(authenticationArgCaptor.capture());
|
||||
|
||||
OAuth2LoginAuthenticationToken authentication = (OAuth2LoginAuthenticationToken) authenticationArgCaptor.getValue();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authentication.getAuthorizationExchange().getAuthorizationRequest();
|
||||
OAuth2AuthorizationResponse authorizationResponse = authentication.getAuthorizationExchange().getAuthorizationResponse();
|
||||
|
||||
String expectedRedirectUri = "http://example.com/login/oauth2/code/registration-2";
|
||||
assertThat(authorizationRequest.getRedirectUri()).isEqualTo(expectedRedirectUri);
|
||||
assertThat(authorizationResponse.getRedirectUri()).isEqualTo(expectedRedirectUri);
|
||||
}
|
||||
|
||||
// gh-5756
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationResponseHasDefaultPort443ThenRedirectUriMatchingExcludesPort() throws Exception {
|
||||
String requestUri = "/login/oauth2/code/" + this.registration2.getRegistrationId();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setScheme("https");
|
||||
request.setServerName("example.com");
|
||||
request.setServerPort(443);
|
||||
request.setServletPath(requestUri);
|
||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.setUpAuthorizationRequest(request, response, this.registration2);
|
||||
this.setUpAuthenticationResult(this.registration2);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
ArgumentCaptor<Authentication> authenticationArgCaptor = ArgumentCaptor.forClass(Authentication.class);
|
||||
verify(this.authenticationManager).authenticate(authenticationArgCaptor.capture());
|
||||
|
||||
OAuth2LoginAuthenticationToken authentication = (OAuth2LoginAuthenticationToken) authenticationArgCaptor.getValue();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authentication.getAuthorizationExchange().getAuthorizationRequest();
|
||||
OAuth2AuthorizationResponse authorizationResponse = authentication.getAuthorizationExchange().getAuthorizationResponse();
|
||||
|
||||
String expectedRedirectUri = "https://example.com/login/oauth2/code/registration-2";
|
||||
assertThat(authorizationRequest.getRedirectUri()).isEqualTo(expectedRedirectUri);
|
||||
assertThat(authorizationResponse.getRedirectUri()).isEqualTo(expectedRedirectUri);
|
||||
}
|
||||
|
||||
// gh-5756
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationResponseHasNonDefaultPortThenRedirectUriMatchingIncludesPort() throws Exception {
|
||||
String requestUri = "/login/oauth2/code/" + this.registration2.getRegistrationId();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setScheme("https");
|
||||
request.setServerName("example.com");
|
||||
request.setServerPort(9090);
|
||||
request.setServletPath(requestUri);
|
||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.setUpAuthorizationRequest(request, response, this.registration2);
|
||||
this.setUpAuthenticationResult(this.registration2);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
ArgumentCaptor<Authentication> authenticationArgCaptor = ArgumentCaptor.forClass(Authentication.class);
|
||||
verify(this.authenticationManager).authenticate(authenticationArgCaptor.capture());
|
||||
|
||||
OAuth2LoginAuthenticationToken authentication = (OAuth2LoginAuthenticationToken) authenticationArgCaptor.getValue();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authentication.getAuthorizationExchange().getAuthorizationRequest();
|
||||
OAuth2AuthorizationResponse authorizationResponse = authentication.getAuthorizationExchange().getAuthorizationResponse();
|
||||
|
||||
String expectedRedirectUri = "https://example.com:9090/login/oauth2/code/registration-2";
|
||||
assertThat(authorizationRequest.getRedirectUri()).isEqualTo(expectedRedirectUri);
|
||||
assertThat(authorizationResponse.getRedirectUri()).isEqualTo(expectedRedirectUri);
|
||||
}
|
||||
|
||||
private void setUpAuthorizationRequest(HttpServletRequest request, HttpServletResponse response,
|
||||
ClientRegistration registration) {
|
||||
OAuth2AuthorizationRequest authorizationRequest = mock(OAuth2AuthorizationRequest.class);
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID, registration.getRegistrationId());
|
||||
when(authorizationRequest.getAdditionalParameters()).thenReturn(additionalParameters);
|
||||
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri(registration.getProviderDetails().getAuthorizationUri())
|
||||
.clientId(registration.getClientId())
|
||||
.redirectUri(expandRedirectUri(request, registration))
|
||||
.scopes(registration.getScopes())
|
||||
.state("state")
|
||||
.additionalParameters(additionalParameters)
|
||||
.build();
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
|
||||
}
|
||||
|
||||
private String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration) {
|
||||
String baseUrl = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
||||
.replaceQuery(null)
|
||||
.replacePath(request.getContextPath())
|
||||
.build()
|
||||
.toUriString();
|
||||
|
||||
Map<String, String> uriVariables = new HashMap<>();
|
||||
uriVariables.put("baseUrl", baseUrl);
|
||||
uriVariables.put("registrationId", clientRegistration.getRegistrationId());
|
||||
|
||||
return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUriTemplate())
|
||||
.buildAndExpand(uriVariables)
|
||||
.toUriString();
|
||||
}
|
||||
|
||||
private void setUpAuthenticationResult(ClientRegistration registration) {
|
||||
OAuth2User user = mock(OAuth2User.class);
|
||||
when(user.getName()).thenReturn(this.principalName1);
|
||||
|
||||
+12
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* 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.
|
||||
@@ -38,14 +38,14 @@ public abstract class AbstractOAuth2Token implements Serializable {
|
||||
* Sub-class constructor.
|
||||
*
|
||||
* @param tokenValue the token value
|
||||
* @param issuedAt the time at which the token was issued
|
||||
* @param expiresAt the expiration time on or after which the token MUST NOT be accepted
|
||||
* @param issuedAt the time at which the token was issued, may be null
|
||||
* @param expiresAt the expiration time on or after which the token MUST NOT be accepted, may be null
|
||||
*/
|
||||
protected AbstractOAuth2Token(String tokenValue, Instant issuedAt, Instant expiresAt) {
|
||||
Assert.hasText(tokenValue, "tokenValue cannot be empty");
|
||||
Assert.notNull(issuedAt, "issuedAt cannot be null");
|
||||
Assert.notNull(expiresAt, "expiresAt cannot be null");
|
||||
Assert.isTrue(expiresAt.isAfter(issuedAt), "expiresAt must be after issuedAt");
|
||||
if (issuedAt != null && expiresAt != null) {
|
||||
Assert.isTrue(expiresAt.isAfter(issuedAt), "expiresAt must be after issuedAt");
|
||||
}
|
||||
this.tokenValue = tokenValue;
|
||||
this.issuedAt = issuedAt;
|
||||
this.expiresAt = expiresAt;
|
||||
@@ -63,7 +63,7 @@ public abstract class AbstractOAuth2Token implements Serializable {
|
||||
/**
|
||||
* Returns the time at which the token was issued.
|
||||
*
|
||||
* @return the time the token was issued
|
||||
* @return the time the token was issued or null
|
||||
*/
|
||||
public Instant getIssuedAt() {
|
||||
return this.issuedAt;
|
||||
@@ -72,7 +72,7 @@ public abstract class AbstractOAuth2Token implements Serializable {
|
||||
/**
|
||||
* Returns the expiration time on or after which the token MUST NOT be accepted.
|
||||
*
|
||||
* @return the expiration time of the token
|
||||
* @return the expiration time of the token or null
|
||||
*/
|
||||
public Instant getExpiresAt() {
|
||||
return this.expiresAt;
|
||||
@@ -92,17 +92,17 @@ public abstract class AbstractOAuth2Token implements Serializable {
|
||||
if (!this.getTokenValue().equals(that.getTokenValue())) {
|
||||
return false;
|
||||
}
|
||||
if (!this.getIssuedAt().equals(that.getIssuedAt())) {
|
||||
if (this.getIssuedAt() != null ? !this.getIssuedAt().equals(that.getIssuedAt()) : that.getIssuedAt() != null) {
|
||||
return false;
|
||||
}
|
||||
return this.getExpiresAt().equals(that.getExpiresAt());
|
||||
return this.getExpiresAt() != null ? this.getExpiresAt().equals(that.getExpiresAt()) : that.getExpiresAt() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.getTokenValue().hashCode();
|
||||
result = 31 * result + this.getIssuedAt().hashCode();
|
||||
result = 31 * result + this.getExpiresAt().hashCode();
|
||||
result = 31 * result + (this.getIssuedAt() != null ? this.getIssuedAt().hashCode() : 0);
|
||||
result = 31 * result + (this.getExpiresAt() != null ? this.getExpiresAt().hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
+20
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* 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.
|
||||
@@ -21,6 +21,7 @@ import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -52,13 +53,17 @@ public interface ClaimAccessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the claim value as a {@code String} or {@code null} if it does not exist.
|
||||
* Returns the claim value as a {@code String} or {@code null} if it does not exist or is equal to {@code null}.
|
||||
*
|
||||
* @param claim the name of the claim
|
||||
* @return the claim value or {@code null} if it does not exist
|
||||
* @return the claim value or {@code null} if it does not exist or is equal to {@code null}
|
||||
*/
|
||||
default String getClaimAsString(String claim) {
|
||||
return (this.containsClaim(claim) ? this.getClaims().get(claim).toString() : null);
|
||||
if (!this.containsClaim(claim)) {
|
||||
return null;
|
||||
}
|
||||
Object claimValue = this.getClaims().get(claim);
|
||||
return (claimValue != null ? claimValue.toString() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,11 +86,18 @@ public interface ClaimAccessor {
|
||||
if (!this.containsClaim(claim)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Instant.ofEpochMilli(Long.valueOf(this.getClaimAsString(claim)));
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException("Unable to convert claim '" + claim + "' to Instant: " + ex.getMessage(), ex);
|
||||
Object claimValue = this.getClaims().get(claim);
|
||||
if (Long.class.isAssignableFrom(claimValue.getClass())) {
|
||||
return Instant.ofEpochSecond((Long) claimValue);
|
||||
}
|
||||
if (Date.class.isAssignableFrom(claimValue.getClass())) {
|
||||
return ((Date) claimValue).toInstant();
|
||||
}
|
||||
if (Instant.class.isAssignableFrom(claimValue.getClass())) {
|
||||
return (Instant) claimValue;
|
||||
}
|
||||
throw new IllegalArgumentException("Unable to convert claim '" + claim +
|
||||
"' of type '" + claimValue.getClass() + "' to Instant.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* 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.
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.core;
|
||||
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
@@ -90,7 +92,8 @@ public class OAuth2AccessToken extends AbstractOAuth2Token {
|
||||
*
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-7.1">Section 7.1 Access Token Types</a>
|
||||
*/
|
||||
public static final class TokenType {
|
||||
public static final class TokenType implements Serializable {
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
public static final TokenType BEARER = new TokenType("Bearer");
|
||||
private final String value;
|
||||
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.oauth2.core;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ClaimAccessor}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class ClaimAccessorTests {
|
||||
private Map<String, Object> claims = new HashMap<>();
|
||||
private ClaimAccessor claimAccessor = (() -> this.claims);
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.claims.clear();
|
||||
}
|
||||
|
||||
// gh-5192
|
||||
@Test
|
||||
public void getClaimAsInstantWhenDateTypeThenReturnInstant() {
|
||||
Instant expectedClaimValue = Instant.now();
|
||||
String claimName = "date";
|
||||
this.claims.put(claimName, Date.from(expectedClaimValue));
|
||||
|
||||
assertThat(this.claimAccessor.getClaimAsInstant(claimName)).isBetween(
|
||||
expectedClaimValue.minusSeconds(1), expectedClaimValue.plusSeconds(1));
|
||||
}
|
||||
|
||||
// gh-5191
|
||||
@Test
|
||||
public void getClaimAsInstantWhenLongTypeSecondsThenReturnInstant() {
|
||||
Instant expectedClaimValue = Instant.now();
|
||||
String claimName = "longSeconds";
|
||||
this.claims.put(claimName, expectedClaimValue.getEpochSecond());
|
||||
|
||||
assertThat(this.claimAccessor.getClaimAsInstant(claimName)).isBetween(
|
||||
expectedClaimValue.minusSeconds(1), expectedClaimValue.plusSeconds(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getClaimAsInstantWhenInstantTypeThenReturnInstant() {
|
||||
Instant expectedClaimValue = Instant.now();
|
||||
String claimName = "instant";
|
||||
this.claims.put(claimName, expectedClaimValue);
|
||||
|
||||
assertThat(this.claimAccessor.getClaimAsInstant(claimName)).isBetween(
|
||||
expectedClaimValue.minusSeconds(1), expectedClaimValue.plusSeconds(1));
|
||||
}
|
||||
|
||||
// gh-5608
|
||||
@Test
|
||||
public void getClaimAsStringWhenValueIsNullThenReturnNull() {
|
||||
String claimName = "claim-with-null-value";
|
||||
this.claims.put(claimName, null);
|
||||
|
||||
assertThat(this.claimAccessor.getClaimAsString(claimName)).isEqualTo(null);
|
||||
}
|
||||
}
|
||||
+18
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.security.oauth2.core;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.util.SerializationUtils;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
@@ -51,16 +52,6 @@ public class OAuth2AccessTokenTests {
|
||||
new OAuth2AccessToken(TOKEN_TYPE, null, ISSUED_AT, EXPIRES_AT);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenIssuedAtIsNullThenThrowIllegalArgumentException() {
|
||||
new OAuth2AccessToken(TOKEN_TYPE, TOKEN_VALUE, null, EXPIRES_AT);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenExpiresAtIsNullThenThrowIllegalArgumentException() {
|
||||
new OAuth2AccessToken(TOKEN_TYPE, TOKEN_VALUE, ISSUED_AT, null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenIssuedAtAfterExpiresAtThenThrowIllegalArgumentException() {
|
||||
new OAuth2AccessToken(TOKEN_TYPE, TOKEN_VALUE, Instant.from(EXPIRES_AT).plusSeconds(1), EXPIRES_AT);
|
||||
@@ -82,4 +73,20 @@ public class OAuth2AccessTokenTests {
|
||||
assertThat(accessToken.getExpiresAt()).isEqualTo(EXPIRES_AT);
|
||||
assertThat(accessToken.getScopes()).isEqualTo(SCOPES);
|
||||
}
|
||||
|
||||
// gh-5492
|
||||
@Test
|
||||
public void constructorWhenCreatedThenIsSerializableAndDeserializable() {
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
TOKEN_TYPE, TOKEN_VALUE, ISSUED_AT, EXPIRES_AT, SCOPES);
|
||||
byte[] serialized = SerializationUtils.serialize(accessToken);
|
||||
accessToken = (OAuth2AccessToken) SerializationUtils.deserialize(serialized);
|
||||
|
||||
assertThat(serialized).isNotNull();
|
||||
assertThat(accessToken.getTokenType()).isEqualTo(TOKEN_TYPE);
|
||||
assertThat(accessToken.getTokenValue()).isEqualTo(TOKEN_VALUE);
|
||||
assertThat(accessToken.getIssuedAt()).isEqualTo(ISSUED_AT);
|
||||
assertThat(accessToken.getExpiresAt()).isEqualTo(EXPIRES_AT);
|
||||
assertThat(accessToken.getScopes()).isEqualTo(SCOPES);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-11
@@ -82,16 +82,6 @@ public class OidcIdTokenTests {
|
||||
new OidcIdToken(null, Instant.ofEpochMilli(IAT_VALUE), Instant.ofEpochMilli(EXP_VALUE), CLAIMS);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenIssuedAtIsNullThenThrowIllegalArgumentException() {
|
||||
new OidcIdToken(ID_TOKEN_VALUE, null, Instant.ofEpochMilli(EXP_VALUE), CLAIMS);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenExpiresAtIsNullThenThrowIllegalArgumentException() {
|
||||
new OidcIdToken(ID_TOKEN_VALUE, Instant.ofEpochMilli(IAT_VALUE), null, CLAIMS);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenClaimsIsEmptyThenThrowIllegalArgumentException() {
|
||||
new OidcIdToken(ID_TOKEN_VALUE, Instant.ofEpochMilli(IAT_VALUE),
|
||||
@@ -110,7 +100,7 @@ public class OidcIdTokenTests {
|
||||
assertThat(idToken.getAudience()).isEqualTo(AUD_VALUE);
|
||||
assertThat(idToken.getIssuedAt().toEpochMilli()).isEqualTo(IAT_VALUE);
|
||||
assertThat(idToken.getExpiresAt().toEpochMilli()).isEqualTo(EXP_VALUE);
|
||||
assertThat(idToken.getAuthenticatedAt().toEpochMilli()).isEqualTo(AUTH_TIME_VALUE);
|
||||
assertThat(idToken.getAuthenticatedAt().getEpochSecond()).isEqualTo(AUTH_TIME_VALUE);
|
||||
assertThat(idToken.getNonce()).isEqualTo(NONCE_VALUE);
|
||||
assertThat(idToken.getAuthenticationContextClass()).isEqualTo(ACR_VALUE);
|
||||
assertThat(idToken.getAuthenticationMethods()).isEqualTo(AMR_VALUE);
|
||||
|
||||
+1
-1
@@ -137,6 +137,6 @@ public class OidcUserInfoTests {
|
||||
assertThat(userInfo.getPhoneNumber()).isEqualTo(PHONE_NUMBER_VALUE);
|
||||
assertThat(userInfo.getPhoneNumberVerified()).isEqualTo(PHONE_NUMBER_VERIFIED_VALUE);
|
||||
assertThat(userInfo.getAddress()).isEqualTo(new DefaultAddressStandardClaim.Builder(ADDRESS_VALUE).build());
|
||||
assertThat(userInfo.getUpdatedAt().toEpochMilli()).isEqualTo(UPDATED_AT_VALUE);
|
||||
assertThat(userInfo.getUpdatedAt().getEpochSecond()).isEqualTo(UPDATED_AT_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,6 @@ dependencies {
|
||||
compile project(':spring-security-oauth2-core')
|
||||
compile springCoreDependency
|
||||
compile 'com.nimbusds:nimbus-jose-jwt'
|
||||
|
||||
testCompile powerMock2Dependencies
|
||||
}
|
||||
|
||||
+25
-6
@@ -26,6 +26,7 @@ import com.nimbusds.jose.util.ResourceRetriever;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.JWTParser;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
|
||||
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
|
||||
@@ -95,20 +96,38 @@ public final class NimbusJwtDecoderJwkSupport implements JwtDecoder {
|
||||
|
||||
@Override
|
||||
public Jwt decode(String token) throws JwtException {
|
||||
JWT jwt = this.parse(token);
|
||||
if ( jwt instanceof SignedJWT ) {
|
||||
return this.createJwt(token, jwt);
|
||||
}
|
||||
|
||||
throw new JwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
|
||||
}
|
||||
|
||||
private JWT parse(String token) {
|
||||
try {
|
||||
return JWTParser.parse(token);
|
||||
} catch (Exception ex) {
|
||||
throw new JwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Jwt createJwt(String token, JWT parsedJwt) {
|
||||
Jwt jwt;
|
||||
|
||||
try {
|
||||
JWT parsedJwt = JWTParser.parse(token);
|
||||
|
||||
// Verify the signature
|
||||
JWTClaimsSet jwtClaimsSet = this.jwtProcessor.process(parsedJwt, null);
|
||||
|
||||
Instant expiresAt = jwtClaimsSet.getExpirationTime().toInstant();
|
||||
Instant issuedAt;
|
||||
Instant expiresAt = null;
|
||||
if (jwtClaimsSet.getExpirationTime() != null) {
|
||||
expiresAt = jwtClaimsSet.getExpirationTime().toInstant();
|
||||
}
|
||||
Instant issuedAt = null;
|
||||
if (jwtClaimsSet.getIssueTime() != null) {
|
||||
issuedAt = jwtClaimsSet.getIssueTime().toInstant();
|
||||
} else {
|
||||
// issuedAt is required in AbstractOAuth2Token so let's default to expiresAt - 1 second
|
||||
} else if (expiresAt != null) {
|
||||
// Default to expiresAt - 1 second
|
||||
issuedAt = Instant.from(expiresAt).minusSeconds(1);
|
||||
}
|
||||
|
||||
|
||||
+1
-11
@@ -72,16 +72,6 @@ public class JwtTests {
|
||||
new Jwt(null, Instant.ofEpochMilli(IAT_VALUE), Instant.ofEpochMilli(EXP_VALUE), HEADERS, CLAIMS);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenIssuedAtIsNullThenThrowIllegalArgumentException() {
|
||||
new Jwt(JWT_TOKEN_VALUE, null, Instant.ofEpochMilli(EXP_VALUE), HEADERS, CLAIMS);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenExpiresAtIsNullThenThrowIllegalArgumentException() {
|
||||
new Jwt(JWT_TOKEN_VALUE, Instant.ofEpochMilli(IAT_VALUE), null, HEADERS, CLAIMS);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenHeadersIsEmptyThenThrowIllegalArgumentException() {
|
||||
new Jwt(JWT_TOKEN_VALUE, Instant.ofEpochMilli(IAT_VALUE),
|
||||
@@ -106,7 +96,7 @@ public class JwtTests {
|
||||
assertThat(jwt.getSubject()).isEqualTo(SUB_VALUE);
|
||||
assertThat(jwt.getAudience()).isEqualTo(AUD_VALUE);
|
||||
assertThat(jwt.getExpiresAt().toEpochMilli()).isEqualTo(EXP_VALUE);
|
||||
assertThat(jwt.getNotBefore().toEpochMilli()).isEqualTo(NBF_VALUE);
|
||||
assertThat(jwt.getNotBefore().getEpochSecond()).isEqualTo(NBF_VALUE);
|
||||
assertThat(jwt.getIssuedAt().toEpochMilli()).isEqualTo(IAT_VALUE);
|
||||
assertThat(jwt.getId()).isEqualTo(JTI_VALUE);
|
||||
}
|
||||
|
||||
+67
-8
@@ -15,36 +15,95 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.jwt;
|
||||
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.JWTParser;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.powermock.api.mockito.PowerMockito.mockStatic;
|
||||
import static org.powermock.api.mockito.PowerMockito.when;
|
||||
import static org.powermock.api.mockito.PowerMockito.whenNew;
|
||||
|
||||
/**
|
||||
* Tests for {@link NimbusJwtDecoderJwkSupport}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest({NimbusJwtDecoderJwkSupport.class, JWTParser.class})
|
||||
public class NimbusJwtDecoderJwkSupportTests {
|
||||
private static final String JWK_SET_URL = "https://provider.com/oauth2/keys";
|
||||
private static final String JWS_ALGORITHM = JwsAlgorithms.RS256;
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
private String unsignedToken = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOi0yMDMzMjI0OTcsImp0aSI6IjEyMyIsInR5cCI6IkpXVCJ9.";
|
||||
|
||||
@Test
|
||||
public void constructorWhenJwkSetUrlIsNullThenThrowIllegalArgumentException() {
|
||||
new NimbusJwtDecoderJwkSupport(null);
|
||||
assertThatThrownBy(() -> new NimbusJwtDecoderJwkSupport(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void constructorWhenJwkSetUrlInvalidThenThrowIllegalArgumentException() {
|
||||
new NimbusJwtDecoderJwkSupport("invalid.com");
|
||||
assertThatThrownBy(() -> new NimbusJwtDecoderJwkSupport("invalid.com"))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void constructorWhenJwsAlgorithmIsNullThenThrowIllegalArgumentException() {
|
||||
new NimbusJwtDecoderJwkSupport(JWK_SET_URL, null);
|
||||
assertThatThrownBy(() -> new NimbusJwtDecoderJwkSupport(JWK_SET_URL, null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test(expected = JwtException.class)
|
||||
@Test
|
||||
public void decodeWhenJwtInvalidThenThrowJwtException() {
|
||||
NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(JWK_SET_URL, JWS_ALGORITHM);
|
||||
jwtDecoder.decode("invalid");
|
||||
assertThatThrownBy(() -> jwtDecoder.decode("invalid"))
|
||||
.isInstanceOf(JwtException.class);
|
||||
}
|
||||
|
||||
// gh-5168
|
||||
@Test
|
||||
public void decodeWhenExpClaimNullThenDoesNotThrowException() throws Exception {
|
||||
SignedJWT jwt = mock(SignedJWT.class);
|
||||
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.parse(JWS_ALGORITHM)).build();
|
||||
when(jwt.getHeader()).thenReturn(header);
|
||||
|
||||
mockStatic(JWTParser.class);
|
||||
when(JWTParser.parse(anyString())).thenReturn(jwt);
|
||||
|
||||
DefaultJWTProcessor jwtProcessor = mock(DefaultJWTProcessor.class);
|
||||
whenNew(DefaultJWTProcessor.class).withAnyArguments().thenReturn(jwtProcessor);
|
||||
|
||||
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().audience("resource1").build();
|
||||
when(jwtProcessor.process(any(JWT.class), eq(null))).thenReturn(jwtClaimsSet);
|
||||
|
||||
NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(JWK_SET_URL, JWS_ALGORITHM);
|
||||
assertThatCode(() -> jwtDecoder.decode("encoded-jwt")).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// gh-5457
|
||||
@Test
|
||||
public void decodeWhenPlainJwtThenExceptionDoesNotMentionClass() throws Exception {
|
||||
NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(JWK_SET_URL, JWS_ALGORITHM);
|
||||
|
||||
assertThatCode(() -> jwtDecoder.decode(this.unsignedToken))
|
||||
.isInstanceOf(JwtException.class)
|
||||
.hasMessageContaining("Unsupported algorithm of none");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
ext['thymeleaf.version'] = '3.0.9.RELEASE'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-security-config')
|
||||
compile project(':spring-security-web')
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
ext['thymeleaf.version'] = '3.0.9.RELEASE'
|
||||
|
||||
dependencies {
|
||||
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||
compile 'org.springframework.boot:spring-boot-starter-web'
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
ext['thymeleaf.version'] = '3.0.9.RELEASE'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-security-config')
|
||||
compile project(':spring-security-oauth2-client')
|
||||
|
||||
@@ -7,6 +7,7 @@ dependencies {
|
||||
compile project(':spring-security-web')
|
||||
compile 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api'
|
||||
compile 'javax.validation:validation-api'
|
||||
compile 'javax.xml.bind:jaxb-api'
|
||||
compile 'org.hibernate:hibernate-validator'
|
||||
compile 'org.springframework:spring-jdbc'
|
||||
compile 'org.springframework:spring-webmvc'
|
||||
|
||||
+4
-17
@@ -18,25 +18,19 @@ package org.springframework.security.samples.config;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*;
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.samples.mvc.config.WebMvcConfiguration;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
||||
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
|
||||
import org.springframework.test.context.web.ServletTestExecutionListener;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
@@ -50,25 +44,18 @@ import org.springframework.web.context.WebApplicationContext;
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = { RootConfiguration.class, WebMvcConfiguration.class })
|
||||
@WebAppConfiguration
|
||||
@TestExecutionListeners(listeners = { ServletTestExecutionListener.class,
|
||||
DependencyInjectionTestExecutionListener.class,
|
||||
DirtiesContextTestExecutionListener.class,
|
||||
TransactionalTestExecutionListener.class,
|
||||
WithSecurityContextTestExecutionListener.class })
|
||||
public class SecurityConfigTests {
|
||||
private MockMvc mvc;
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private Filter springSecurityFilterChain;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mvc = MockMvcBuilders.webAppContextSetup(context)
|
||||
.addFilters(springSecurityFilterChain)
|
||||
.defaultRequest(get("/").with(testSecurityContext())).build();
|
||||
.apply(springSecurity())
|
||||
.defaultRequest(get("/").accept(MediaType.TEXT_HTML)).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -7,6 +7,7 @@ dependencies {
|
||||
compile project(':spring-security-web')
|
||||
compile 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api'
|
||||
compile 'javax.validation:validation-api'
|
||||
compile 'javax.xml.bind:jaxb-api'
|
||||
compile 'org.hibernate:hibernate-validator'
|
||||
compile 'org.springframework:spring-jdbc'
|
||||
compile 'org.springframework:spring-webmvc'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user