1
0
mirror of synced 2026-07-08 12:20:02 +00:00

Compare commits

...

89 Commits

Author SHA1 Message Date
Rob Winch 09429479fe Release 5.0.6.RELEASE 2018-06-12 21:23:02 -05:00
Rob Winch dc602c0840 Update to Spring Framework 5.0.7
Fixes: gh-5425
2018-06-12 16:51:01 -05:00
Rob Winch fedc183b5b Fix htmlunit
Fixes: gh-5426
2018-06-12 16:50:52 -05:00
Rob Winch d3f12f0ee7 Update to Selenium 3.12.0
Fixes: gh-5427
2018-06-12 16:50:48 -05:00
Rob Winch 2a723cf5c7 Update to htmlunit-driver:2.31.0
Fixes: gh-5428
2018-06-12 16:50:42 -05:00
Rob Winch 6f32303866 Update to hibernate-validator:6.0.10.Final
Fixes: gh-5429
2018-06-12 16:50:34 -05:00
Rob Winch 400adaef1c Update to Hibernate 5.2.17
Fixes: gh-5430
2018-06-12 16:50:29 -05:00
Rob Winch 9924d6e920 Update to hsqldb:2.4.1
Fixes: gh-5431
2018-06-12 16:50:23 -05:00
Rob Winch 6cfd30aee8 Update to assertj-core:3.10.0
Fixes: gh-5432
2018-06-12 16:50:15 -05:00
Rob Winch 1abdb69615 Update to htmlunit:2.31
Fixes: gh-5433
2018-06-12 16:49:23 -05:00
Rob Winch b07f6b0178 Update to unboundid-ldapsdk:4.0.6
Fixes: gh-54234
2018-06-12 16:49:10 -05:00
Rob Winch f0eec1b03b Update to oauth2-oidc-sdk:5.62
Fixes: gh-5435
2018-06-12 16:48:57 -05:00
Rob Winch 6ca6d80184 Update to nimbus-jose-jwt:5.11
Fixes: gh-5436
2018-06-12 16:47:35 -05:00
Rob Winch ce5eec8761 Update to Jackson 2.9.6
Fixes: gh-5424
2018-06-12 16:47:30 -05:00
Rob Winch 86e03b3fb4 Update to Spring Boot 2.0.2.RELEASE
Fixes: gh-5423
2018-06-12 16:47:26 -05:00
Rob Winch c909264b31 Update GAE to 1.9.64
Fixes: gh-5422
2018-06-12 16:47:21 -05:00
Rob Winch f52ab9a146 Update to Reactor Bismuth SR10
Fixes: gh-5421
2018-06-12 16:47:06 -05:00
Rob Winch ad2cb501b8 Add cross references to ReactorContextTestExecutionListener
Fixes: gh-5418
2018-06-11 17:16:15 -05:00
Rob Winch 8659a4b555 Add UserDetailsRepositoryReactiveAuthenticationManager.setScheduler
Fixes: gh-5417
2018-06-11 14:30:11 -05:00
Joe Grandja e04b29426b DefaultLoginPageGeneratingFilter escapes OAuth2 ClientRegistrations
Fixes gh-5394
2018-05-29 09:53:31 -04:00
Josh Cummings 350d434e28 Next Development Version 2018-05-08 09:25:49 -06:00
Josh Cummings bbfc3d2b4b Release 5.0.5.RELEASE 2018-05-08 09:22:58 -06:00
Rob Winch a7e4f36dbe Update to Spring Data Kay SR7
Fixes: gh-5318
2018-05-08 09:04:23 -05:00
Rob Winch e61aafe115 Update to Reactor Bismuth SR9
Fixes: gh-5319
2018-05-08 09:04:05 -05:00
Rob Winch ff95388ce1 Update to Spring 5.0.6
Fixes: gh-5290
2018-05-08 08:34:35 -05:00
Rob Winch 545228cf8d Documentation typo fixes
Fixes: gh-5317
2018-05-07 16:48:03 -05:00
Denys Ivano 7b8fa90d96 Add accessDeniedHandler method to ExceptionHandlingSpec
This allows to configure accessDeniedHandler in ExceptionTranslationWebFilter through ServerHttpSecurity.

Issue: gh-5257
2018-05-07 16:23:32 -05:00
Alexander Münch c30e218f1f Avoid unnecessary grow of ArrayList
Adapted ArrayList size in CacheControlHeadersWriter::createHeaders()

Fixes: gh-5310
2018-05-04 14:43:17 -05:00
XYUU 70d284865f DefaultLoginPageGeneratingFilter should calculate ContentLength using UTF-8
Fixes: gh-5309
2018-05-04 14:43:17 -05:00
Johnny Lim a4b5523063 Fix 'attributes' deprecation warning in spring-security-docs-guides
Fixes: gh-5308
2018-05-04 14:13:44 -05:00
Kazuki Shimizu 8883ec17e5 Fix JdbcDaoImpl Javadoc
Fix incorrect explanation for customizing query on JdbcDaoImpl

Issue: gh-5306
2018-05-04 10:48:55 -05:00
Joe Grandja f524da3edc Add test NimbusUserInfoResponseClient sets Accept header to JSON
Issue gh-5294
2018-05-03 20:23:03 -04:00
Joe Grandja e1e464387e NimbusUserInfoResponseClient sets Accept header to JSON
Fixes gh-5294
2018-05-03 20:22:47 -04:00
Rob Winch a5dc48f9d9 Improve PasswordEncoder deprecated notices
Fixes: gh-5296
2018-05-03 15:13:20 -05:00
Rob Winch 9b722a9c85 Update to Spring Framework 5.0.6.BUILD-SNAPSHOT
Issue: gh-5290
2018-05-03 11:01:37 -05:00
Rob Winch 884a4e46df Update to selenium 3.11.0
Fixes: gh-5289
2018-05-03 11:01:37 -05:00
Rob Winch 01c2d57d46 Update to mockito-core:2.18.3
Fixes: gh-5284
2018-05-03 11:01:37 -05:00
Rob Winch c9e42ce756 Update to htmlunit:2.30
Fixes: gh-5283
2018-05-03 11:01:37 -05:00
Rob Winch 9b0b933736 Update to aspectj 1.9.1
Fixes: gh-5282
2018-05-03 11:01:37 -05:00
Rob Winch 0169b8746b Update to javax.servlet-api:4.0.1
Fixes: gh-5281
2018-05-03 11:01:37 -05:00
Rob Winch 25a87051a6 Update to oauth2-oidc-sdk:5.61
Fixes: gh-5280
2018-05-03 11:01:37 -05:00
Rob Winch 7edf9fc6c8 Update to nimbus-jose-jwt:5.10
Fixes: gh-5279
2018-05-03 11:01:37 -05:00
Rob Winch a06f1e3936 Update Spring Boot 2.0.1.RELEASE
Fixes: gh-5278
2018-05-03 11:01:37 -05:00
Rob Winch b296d2eaa5 Update GAE 1.9.63
Fixes: gh-5277
2018-05-03 11:01:37 -05:00
Rob Winch 11a8c76991 Add update-dependencies.sh
Fixes: gh-5276
2018-05-03 11:01:37 -05:00
Rob Winch 2f441f18e6 Make MIN_SPRING_VERSION Dynamic
Fixes: gh-5065

# Conflicts:
#	core/src/main/java/org/springframework/security/core/SpringSecurityCoreVersion.java
2018-05-03 11:01:27 -05:00
Joe Grandja a7de1e363f OAuth2LoginAuthenticationFilter should handle null ClientRegistration
Fixes gh-5251
2018-05-02 20:31:59 -04:00
Rob Winch 32f5fb5eb2 ExceptionTranslationFilter does not handle committed responses
Fixes: gh-5273
2018-04-30 16:50:02 -05:00
Rob Winch f7f6798f71 DefaultWebSecurityExpressionHandler uses PermissionEvaluator Bean
The default instance of DefaultWebSecurityExpressionHandler uses the
PermissionEvaluator Bean by default.

Fixes: gh-5272
2018-04-30 12:16:34 -05:00
Joe Grandja 0b72f93027 WebSecurityConfigurationTests groovy->java
Issue: gh-4939
2018-04-30 11:43:30 -05:00
Rob Winch 5b751baf61 Fixes: gh-5190 2018-04-16 17:52:55 -05:00
Joe Grandja ec9ab1965a Next Development Version 2018-04-04 13:36:28 -04:00
Joe Grandja 30aede82da Release 5.0.4.RELEASE 2018-04-04 13:03:55 -04:00
Joe Grandja 2686452b99 Update to aspectjtools:1.9.0
Fixes gh-5211
2018-04-04 13:00:46 -04:00
Joe Grandja 7e470ff5bd Update to aspectjrt:1.9.0
Fixes gh-5210
2018-04-04 12:59:09 -04:00
Joe Grandja df6a6e5991 Update to Reactor Bismuth SR8
Fixes gh-5209
2018-04-04 12:53:54 -04:00
Joe Grandja 1500a22439 Update to Spring Data Kay SR6
Fixes gh-5167
2018-04-04 10:31:04 -04:00
Joe Grandja 64cb256bb9 Update to Spring Framework 5.0.5
Fixes gh-5166
2018-04-04 10:27:43 -04:00
Joe Grandja e6d68acfd6 Fix NPE with exp claim in NimbusJwtDecoderJwkSupport
Fixes gh-5168
2018-04-04 09:02:05 -04:00
Joe Grandja bc2a7ac394 ClaimAccessor.getClaimAsInstant() converts Long or Date
Fixes gh-5191, Fixes gh-5192
2018-04-04 04:57:36 -04:00
Joe Grandja 55019b2f0b Fix SecuredAnnotationSecurityMetadataSourceTests -> Related SPR-16677 2018-04-03 11:16:50 -04:00
Rob Winch 151b545ed0 Polish Javadoc
Fixes: gh-5186
2018-03-29 15:33:31 -05:00
Rob Winch c67ce144b9 Polish HeadersSpec
Fixes: gh-5187
2018-03-29 15:33:31 -05:00
Rob Winch 6729c39905 Fix JDK 9
Issue: gh-5160
2018-03-28 15:00:41 -05:00
Rob Winch 197439f9e1 Update to mockito 2.17.0
Fixes: gh-5181
2018-03-28 14:42:43 -05:00
Rob Winch 70a11267fa Update to hibernate-validator:6.0.9.Final
Fixes: gh-5180
2018-03-28 14:42:35 -05:00
Rob Winch 4e0c165d47 Update to hibernate-entitymanager:5.2.16.Final
Fixes: gh-5178
2018-03-28 14:42:09 -05:00
Rob Winch 93844b1823 Update to assertj 2.9.1
Fixes: gh-5177
2018-03-28 14:32:49 -05:00
Rob Winch a30b3321c4 Update to thymeleaf-layout-dialect:2.3.0
Fixes: gh-5176
2018-03-28 14:32:30 -05:00
Rob Winch ffb8c1978a Update to unbounded 4.0.5
Fixes: gh-5175
2018-03-28 14:32:13 -05:00
Rob Winch 4722ab21bf Update to oauth2-oidc-sdk:5.57
Fixes: gh-5179
2018-03-28 14:32:07 -05:00
Rob Winch 335735e227 Update to nimbus-jose-jwt:5.9
Fixes: gh-5174
2018-03-28 14:31:47 -05:00
Rob Winch 501269a51f Update to appengine 1.9.63
Fixes: gh-5173
2018-03-28 14:30:57 -05:00
Rob Winch 6b58218256 Update to Jackson 2.9.5
Fixes: gh-5172
2018-03-28 14:30:48 -05:00
Rob Winch de1137fc5e Update mockwebserver 3.10.0
Fixes: gh-5171
2018-03-28 14:30:36 -05:00
Rob Winch e68657216c Fixes for SPR-16624
Fixes: gh-5165
2018-03-27 22:36:03 -05:00
Rob Winch bfada59a1e CookieClearingLogoutHandler adds uses contextPath + "/"
Fixes: gh-5140
2018-03-19 16:50:48 -05:00
Rob Winch bc17860cb6 Fix ReactorContextTestExecutionListener with custom SecurityContext
Fixes: gh-5139
2018-03-19 09:33:34 -05:00
Oleh Dokuka 5e351a4f75 ReactorContextTestExecutionListener with multiple Threads
Fixes: gh-5138
2018-03-19 09:32:36 -05:00
Rob Winch 0458ca88e6 Relax assertions in HeaderSpecTests
Fixes: gh-5117
2018-03-15 08:31:21 -05:00
Rob Winch a7a71d6cef Fix @since on GlobalAuthenticationConfigurerAdapter
Fixes: gh-5109
2018-03-13 14:25:40 -05:00
Josh Cummings a7f180b2c7 Authorities authenticate TestingAuthenticationToken
In other extensions of `AbstractAuthenticationToken`, the constructors
that include `authorities` call `setAuthenticated(true)`. This includes
`PreAuthenticated`-, `UsernamePassword`-, and
`RememberMeAuthenticationToken`.

This change brings `TestingAuthenticationToken` in line with that
convention.

Note that this was done once already to one of the constructors
(ee13be4) in `TestingAuthenticationToken` that takes an arity of
`authorities`. It was not propagated to the constructor that takes a
collection, which is what this commit remedies.

Fixes: gh-5098
2018-03-09 13:26:36 -06:00
Josh Cummings 87f266133b Update to Spring Boot 2.0.0.RELEASE
Fixes: gh-5091
2018-03-09 09:45:20 -06:00
Rob Winch 8f8deac0f4 Fix StrictHttpFirewall rules
Fixes: gh-5092
2018-03-08 21:29:31 -06:00
Vedran Pavic 9830d01acc Upgrade Nimbus JOSE + JWT to 5.6
Fixes: gh-5089
2018-03-08 16:43:05 -06:00
Vedran Pavic d8fea5c6f1 Upgrade Nimbus OAuth + OIDC SDK to 5.56
Fixes: gh-5088
2018-03-08 16:42:54 -06:00
Rob Winch 4d410b0b61 Polish Javadoc HttpStatusServerAccessDeniedHandler 2018-03-07 12:34:15 -06:00
Rob Winch 24a4fbfe56 HttpStatusServerAccessDeniedHandler use injected HttpStatus
Fixes: gh-5078
2018-03-07 12:28:45 -06:00
Josh Cummings d5d1c30d1d Update to Gradle 4.6
Only thing of interest really is that the Jacoco Gradle plugin was bumped
to 0.8.0, which requires org.ow2.asm:asm:6.0.

Fixes: gh-5077
2018-03-06 20:27:31 -06:00
136 changed files with 2551 additions and 633 deletions
+8
View File
@@ -30,3 +30,11 @@ gradle.taskGraph.whenReady { graph ->
}
}
}
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)
}
}
@@ -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);
@@ -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"));
+1
View File
@@ -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'
@@ -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)
@@ -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);
@@ -55,7 +55,7 @@ import java.lang.annotation.Target;
* &#064;EnableWebFluxSecurity
* public class MyExplicitSecurityConfiguration {
* &#064;Bean
* SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* .authorizeExchange()
* .anyExchange().authenticated()
@@ -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)
}
}
@@ -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
@@ -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'
@@ -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();
}
}
@@ -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);
}
}
@@ -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() {
+11
View File
@@ -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}
@@ -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");
@@ -25,6 +25,7 @@ import reactor.core.publisher.Mono;
* @author Rob Winch
* @since 5.0
*/
@FunctionalInterface
public interface ReactiveAuthenticationManager {
/**
@@ -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
@@ -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;
}
}
@@ -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");
}
}
@@ -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()));
@@ -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();
@@ -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
*
@@ -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.
@@ -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");
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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());
}
}
@@ -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. */
@@ -39,7 +39,8 @@ import java.util.Base64;
* @deprecated Digest based password encoding is not considered secure. Instead use an
* adaptive one way funciton 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 {
@@ -73,7 +73,8 @@ import java.util.Base64;
* @deprecated Digest based password encoding is not considered secure. Instead use an
* adaptive one way funciton 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 {
@@ -76,7 +76,8 @@ import java.util.Base64;
* @deprecated Digest based password encoding is not considered secure. Instead use an
* adaptive one way funciton 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 {
@@ -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
View File
@@ -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'
}
+15 -18
View File
@@ -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`.
+2 -2
View File
@@ -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
View File
@@ -1,3 +1,3 @@
gaeVersion=1.9.62
springBootVersion=2.0.0.RC2
version=5.0.4.BUILD-SNAPSHOT
gaeVersion=1.9.64
springBootVersion=2.0.2.RELEASE
version=5.0.6.RELEASE
+38 -38
View File
@@ -1,12 +1,12 @@
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-SR10'
mavenBom 'org.springframework:spring-framework-bom:5.0.7.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR7'
}
dependencies {
dependency 'cglib:cglib-nodep:3.2.6'
dependency 'com.squareup.okhttp3:mockwebserver:3.9.1'
dependency 'com.squareup.okhttp3:mockwebserver:3.10.0'
dependency 'opensymphony:sitemesh:2.4.2'
dependency 'org.gebish:geb-spock:0.10.0'
dependency 'org.jasig.cas:cas-server-webapp:4.2.7'
@@ -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.6'
dependency 'com.fasterxml.jackson.core:jackson-core:2.9.6'
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.6'
dependency 'com.fasterxml:classmate:1.3.4'
dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1'
dependency 'com.google.appengine:appengine-api-1.0-sdk:1.9.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.nimbusds:nimbus-jose-jwt:5.11'
dependency 'com.nimbusds:oauth2-oidc-sdk:5.62'
dependency 'com.squareup.okhttp3:okhttp:3.9.0'
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'
dependency 'com.sun.xml.bind:jaxb-impl:2.3.0'
dependency 'com.unboundid:unboundid-ldapsdk:4.0.6'
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'
@@ -62,8 +64,9 @@ dependencyManagement {
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: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'
@@ -72,13 +75,11 @@ dependencyManagement {
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.sourceforge.htmlunit:htmlunit:2.31'
dependency 'net.sourceforge.htmlunit:neko-htmlunit:2.31'
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'
@@ -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.1'
dependency 'org.aspectj:aspectjtools:1.9.1'
dependency 'org.aspectj:aspectjweaver:1.9.1'
dependency 'org.assertj:assertj-core:3.10.0'
dependency 'org.attoparser:attoparser:2.0.4.RELEASE'
dependency 'org.bouncycastle:bcpkix-jdk15on:1.59'
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'
@@ -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.10.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.31.0'
dependency 'org.seleniumhq.selenium:selenium-java:3.12.0'
dependency 'org.seleniumhq.selenium:selenium-support:3.12.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'
+1 -1
View File
@@ -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
@@ -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;
@@ -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;
@@ -97,6 +97,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 +149,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(
@@ -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);
}
}
@@ -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);
}
}
@@ -197,6 +197,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();
@@ -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;
}
}
@@ -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;
@@ -81,11 +82,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.");
}
/**
@@ -0,0 +1,73 @@
/*
* 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));
}
}
@@ -51,16 +51,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,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);
@@ -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
}
@@ -103,12 +103,15 @@ public final class NimbusJwtDecoderJwkSupport implements JwtDecoder {
// 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);
}
@@ -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);
}
@@ -15,36 +15,77 @@
*/
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.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.*;
import static org.mockito.Mockito.mock;
import static org.powermock.api.mockito.PowerMockito.*;
/**
* Tests for {@link NimbusJwtDecoderJwkSupport}.
*
* @author Joe Grandja
*/
@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)
@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 {
JWT jwt = mock(JWT.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();
}
}
@@ -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'
@@ -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'
@@ -16,17 +16,16 @@
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.test.context.ContextConfiguration;
@@ -49,14 +48,11 @@ public class SecurityConfigTests {
@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
@@ -84,7 +80,8 @@ public class SecurityConfigTests {
@Test
@WithMockUser
public void logoutSuccess() throws Exception {
mvc.perform(logout()).andExpect(redirectedUrl("/login?logout"))
mvc.perform(logout())
.andExpect(redirectedUrl("/login?logout"))
.andExpect(unauthenticated());
}
}
}
@@ -24,6 +24,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-webmvc'
compile apachedsDependencies
@@ -8,6 +8,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'
@@ -19,6 +19,7 @@ apply plugin: 'io.spring.convention.spring-sample-war'
dependencies {
compile jstlDependencies
compile slf4jDependencies
compile 'javax.xml.bind:jaxb-api'
providedCompile 'javax.servlet:javax.servlet-api'
providedCompile 'javax.servlet.jsp:javax.servlet.jsp-api'
@@ -5,6 +5,7 @@ dependencies {
compile slf4jDependencies
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'
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
rm -f build/updates.txt
./gradlew dependencyUpdate -Drevision=release
find -name report.txt | xargs cat > build/updates.txt
echo "Updates...."
cat build/updates.txt | fgrep ' ->' | sort | uniq
+1
View File
@@ -14,6 +14,7 @@ dependencies {
testCompile 'com.fasterxml.jackson.core:jackson-databind'
testCompile 'io.projectreactor:reactor-test'
testCompile 'javax.xml.bind:jaxb-api'
testCompile 'org.skyscreamer:jsonassert'
testCompile 'org.springframework:spring-webmvc'
testCompile 'org.springframework:spring-tx'
@@ -36,6 +36,8 @@ import org.springframework.test.context.TestExecutionListeners;
*
* @author Rob Winch
* @since 4.0.2
* @see WithSecurityContextTestExecutionListener
* @see ReactorContextTestExecutionListener
*/
@Documented
@Inherited
@@ -19,6 +19,7 @@ package org.springframework.security.test.context.support;
import org.reactivestreams.Subscription;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.test.context.TestSecurityContextHolder;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
@@ -26,6 +27,7 @@ import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.util.ClassUtils;
import reactor.core.CoreSubscriber;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Operators;
import reactor.util.context.Context;
@@ -35,6 +37,8 @@ import reactor.util.context.Context;
*
* @author Rob Winch
* @since 5.0
* @see WithSecurityContextTestExecutionListener
* @see org.springframework.security.test.context.annotation.SecurityTestExecutionListeners
*/
public class ReactorContextTestExecutionListener
extends DelegatingTestExecutionListener {
@@ -54,7 +58,8 @@ public class ReactorContextTestExecutionListener
private static class DelegateTestExecutionListener extends AbstractTestExecutionListener {
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
Hooks.onLastOperator(Operators.lift((s, sub) -> new SecuritySubContext<>(sub)));
SecurityContext securityContext = TestSecurityContextHolder.getContext();
Hooks.onLastOperator(Operators.lift((s, sub) -> new SecuritySubContext<>(sub, securityContext)));
}
@Override
@@ -66,9 +71,11 @@ public class ReactorContextTestExecutionListener
private static String CONTEXT_DEFAULTED_ATTR_NAME = SecuritySubContext.class.getName().concat(".CONTEXT_DEFAULTED_ATTR_NAME");
private final CoreSubscriber<T> delegate;
private final SecurityContext securityContext;
SecuritySubContext(CoreSubscriber<T> delegate) {
SecuritySubContext(CoreSubscriber<T> delegate, SecurityContext securityContext) {
this.delegate = delegate;
this.securityContext = securityContext;
}
@Override
@@ -78,11 +85,12 @@ public class ReactorContextTestExecutionListener
return context;
}
context = context.put(CONTEXT_DEFAULTED_ATTR_NAME, Boolean.TRUE);
Authentication authentication = TestSecurityContextHolder.getContext().getAuthentication();
Authentication authentication = securityContext.getAuthentication();
if (authentication == null) {
return context;
}
Context toMerge = ReactiveSecurityContextHolder.withAuthentication(authentication);
Context toMerge = ReactiveSecurityContextHolder.withSecurityContext(
Mono.just(this.securityContext));
return toMerge.putAll(context);
}
@@ -42,6 +42,8 @@ import org.springframework.test.web.servlet.MockMvc;
* @author Rob Winch
* @author Eddú Meléndez
* @since 4.0
* @see ReactorContextTestExecutionListener
* @see org.springframework.security.test.context.annotation.SecurityTestExecutionListeners
*/
public class WithSecurityContextTestExecutionListener
extends AbstractTestExecutionListener {
@@ -90,6 +90,7 @@ public final class SecurityMockMvcRequestBuilders {
@Override
public MockHttpServletRequest buildRequest(ServletContext servletContext) {
MockHttpServletRequest request = post(this.logoutUrl)
.accept(MediaType.TEXT_HTML, MediaType.ALL)
.buildRequest(servletContext);
return this.postProcessor.postProcessRequest(request);
}
@@ -21,6 +21,8 @@ package org.springframework.security.test.context.support;
* @since 5.0
*/
import java.util.concurrent.ForkJoinPool;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -92,6 +94,35 @@ public class ReactorContextTestExecutionListenerTests {
assertAuthentication(expectedAuthentication);
}
@Test
public void beforeTestMethodWhenCustomContext() throws Exception {
TestingAuthenticationToken expectedAuthentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
SecurityContext context = new CustomContext(expectedAuthentication);
TestSecurityContextHolder.setContext(context);
this.listener.beforeTestMethod(this.testContext);
assertSecurityContext(context);
}
static class CustomContext implements SecurityContext {
private Authentication authentication;
CustomContext(Authentication authentication) {
this.authentication = authentication;
}
@Override
public Authentication getAuthentication() {
return this.authentication;
}
@Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
}
@Test
public void beforeTestMethodWhenExistingAuthenticationThenReactorContextHasOriginalAuthentication() throws Exception {
TestingAuthenticationToken expectedAuthentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
@@ -153,6 +184,18 @@ public class ReactorContextTestExecutionListenerTests {
assertThat(comparator.compare(withSecurity, reactorContext)).isLessThan(0);
}
@Test
public void checkSecurityContextResolutionWhenSubscribedContextCalledOnTheDifferentThreadThanWithSecurityContextTestExecutionListener() throws Exception {
TestingAuthenticationToken contextHolder = new TestingAuthenticationToken("contextHolder", "password", "ROLE_USER");
TestSecurityContextHolder.setContext(new SecurityContextImpl(contextHolder));
this.listener.beforeTestMethod(this.testContext);
ForkJoinPool.commonPool()
.submit(() -> assertAuthentication(contextHolder))
.join();
}
public void assertAuthentication(Authentication expected) {
Mono<Authentication> authentication = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication);
@@ -161,4 +204,13 @@ public class ReactorContextTestExecutionListenerTests {
.expectNext(expected)
.verifyComplete();
}
private void assertSecurityContext(SecurityContext expected) {
Mono<SecurityContext> securityContext = ReactiveSecurityContextHolder.getContext();
StepVerifier.create(securityContext)
.expectNext(expected)
.verifyComplete();
}
}
@@ -101,7 +101,7 @@ public class WithSecurityContextTestExcecutionListenerTests {
SqlScriptsTestExecutionListener sql = new SqlScriptsTestExecutionListener();
WithSecurityContextTestExecutionListener security = new WithSecurityContextTestExecutionListener();
List<? extends TestExecutionListener> listeners = Arrays.asList(security, sql);
List<TestExecutionListener> listeners = Arrays.asList(security, sql);
AnnotationAwareOrderComparator.sort(listeners);
@@ -52,7 +52,7 @@ abstract class AbstractMockServerConfigurersTests {
@RestController
protected static class PrincipalController {
Principal principal;
volatile Principal principal;
@RequestMapping("/**")
public Principal get(Principal principal) {
@@ -16,6 +16,8 @@
package org.springframework.security.test.web.reactive.server;
import java.util.concurrent.ForkJoinPool;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.http.HttpHeaders;
@@ -114,4 +116,54 @@ public class SecurityMockServerConfigurersAnnotatedTests extends AbstractMockSer
assertPrincipalCreatedFromUserDetails(controller.removePrincipal(), userBuilder.build());
}
@Test
@WithMockUser
public void withMockUserWhenOnMethodAndRequestIsExecutedOnDifferentThreadThenSuccess() {
Authentication authentication = TestSecurityContextHolder.getContext().getAuthentication();
ForkJoinPool
.commonPool()
.submit(() ->
client
.get()
.exchange()
.expectStatus()
.isOk()
)
.join();
controller.assertPrincipalIsEqualTo(authentication);
}
@Test
@WithMockUser
public void withMockUserAndWithCallOnSeparateThreadWhenMutateWithMockPrincipalAndNoMutateThenOverridesAnnotationAndUsesAnnotation() {
TestingAuthenticationToken authentication = new TestingAuthenticationToken("authentication", "secret", "ROLE_USER");
ForkJoinPool
.commonPool()
.submit(() ->
client
.mutateWith(mockAuthentication(authentication))
.get()
.exchange()
.expectStatus().isOk()
)
.join();
controller.assertPrincipalIsEqualTo(authentication);
ForkJoinPool
.commonPool()
.submit(() ->
client
.get()
.exchange()
.expectStatus().isOk()
)
.join();
assertPrincipalCreatedFromUserDetails(controller.removePrincipal(), userBuilder.build());
}
}
@@ -24,6 +24,7 @@ import static org.powermock.api.mockito.PowerMockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
@@ -114,8 +115,8 @@ public class SecurityMockMvcRequestPostProcessorsUserTests {
verify(repository).saveContext(contextCaptor.capture(), eq(request),
any(HttpServletResponse.class));
SecurityContext context = contextCaptor.getValue();
assertThat(context.getAuthentication().getAuthorities()).containsOnly(authority1,
authority2);
assertThat((List<GrantedAuthority>) context.getAuthentication().getAuthorities())
.containsOnly(authority1, authority2);
}
@Test(expected = IllegalArgumentException.class)
@@ -133,12 +134,12 @@ public class SecurityMockMvcRequestPostProcessorsUserTests {
verify(repository).saveContext(contextCaptor.capture(), eq(request),
any(HttpServletResponse.class));
SecurityContext context = contextCaptor.getValue();
assertThat(context.getAuthentication().getAuthorities()).containsOnly(authority1,
authority2);
assertThat((List<GrantedAuthority>) context.getAuthentication().getAuthorities())
.containsOnly(authority1, authority2);
}
private void mockWebTestUtils() {
spy(WebTestUtils.class);
when(WebTestUtils.getSecurityContextRepository(request)).thenReturn(repository);
}
}
}
@@ -27,6 +27,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
@@ -53,7 +54,10 @@ public class AuthenticationTests {
@Before
public void setup() {
mvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
mvc = MockMvcBuilders.webAppContextSetup(context)
.apply(springSecurity())
.defaultRequest(get("/").accept(MediaType.TEXT_HTML))
.build();
}
@Test
+1 -1
View File
@@ -21,12 +21,12 @@ dependencies {
testCompile project(path: ':spring-security-core', configuration: 'tests')
testCompile 'commons-codec:commons-codec'
testCompile 'io.projectreactor:reactor-test'
testCompile 'javax.xml.bind:jaxb-api'
testCompile 'org.codehaus.groovy:groovy-all'
testCompile 'org.skyscreamer:jsonassert'
testCompile 'org.springframework:spring-webflux'
testCompile powerMock2Dependencies
testCompile spockDependencies
testCompile slf4jDependencies
testRuntime 'org.hsqldb:hsqldb'
}
@@ -135,6 +135,9 @@ public class ExceptionTranslationFilter extends GenericFilterBean {
}
if (ase != null) {
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
}
handleSpringSecurityException(request, response, chain, ase);
}
else {
@@ -22,7 +22,6 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A logout handler which clears a defined list of cookies, using the context path as the
@@ -43,10 +42,7 @@ public final class CookieClearingLogoutHandler implements LogoutHandler {
Authentication authentication) {
for (String cookieName : cookiesToClear) {
Cookie cookie = new Cookie(cookieName, null);
String cookiePath = request.getContextPath();
if (!StringUtils.hasLength(cookiePath)) {
cookiePath = "/";
}
String cookiePath = request.getContextPath() + "/";
cookie.setPath(cookiePath);
cookie.setMaxAge(0);
response.addCookie(cookie);
@@ -25,7 +25,6 @@ import java.util.List;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;
import javax.security.auth.Subject;
import org.apache.commons.logging.Log;
@@ -44,6 +43,8 @@ final class DefaultWASUsernameAndGroupsExtractor implements WASUsernameAndGroups
private static final Log logger = LogFactory
.getLog(DefaultWASUsernameAndGroupsExtractor.class);
private static final String PORTABLE_REMOTE_OBJECT_CLASSNAME = "javax.rmi.PortableRemoteObject";
private static final String USER_REGISTRY = "UserRegistry";
private static Method getRunAsSubject = null;
@@ -52,6 +53,8 @@ final class DefaultWASUsernameAndGroupsExtractor implements WASUsernameAndGroups
private static Method getSecurityName = null;
private static Method narrow = null;
// SEC-803
private static Class<?> wsCredentialClass = null;
@@ -80,7 +83,7 @@ final class DefaultWASUsernameAndGroupsExtractor implements WASUsernameAndGroups
.iterator().next();
if (credential != null) {
userSecurityName = (String) invokeMethod(getSecurityNameMethod(),
credential, null);
credential);
}
}
if (logger.isDebugEnabled()) {
@@ -125,8 +128,7 @@ final class DefaultWASUsernameAndGroupsExtractor implements WASUsernameAndGroups
// TODO: Cache UserRegistry object
ic = new InitialContext();
Object objRef = ic.lookup(USER_REGISTRY);
Object userReg = PortableRemoteObject.narrow(objRef,
Class.forName("com.ibm.websphere.security.UserRegistry"));
Object userReg = invokeMethod(getNarrowMethod(), null , objRef, Class.forName("com.ibm.websphere.security.UserRegistry"));
if (logger.isDebugEnabled()) {
logger.debug("Determining WebSphere groups for user " + securityName
+ " using WebSphere UserRegistry " + userReg);
@@ -156,7 +158,7 @@ final class DefaultWASUsernameAndGroupsExtractor implements WASUsernameAndGroups
}
}
private static Object invokeMethod(Method method, Object instance, Object[] args) {
private static Object invokeMethod(Method method, Object instance, Object... args) {
try {
return method.invoke(instance, args);
}
@@ -230,6 +232,13 @@ final class DefaultWASUsernameAndGroupsExtractor implements WASUsernameAndGroups
return getSecurityName;
}
private static Method getNarrowMethod() {
if (narrow == null) {
narrow = getMethod(PORTABLE_REMOTE_OBJECT_CLASSNAME, "narrow", new String[] { Object.class.getName() , Class.class.getName()});
}
return narrow;
}
// SEC-803
private static Class<?> getWSCredentialClass() {
if (wsCredentialClass == null) {
@@ -22,6 +22,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
import org.springframework.web.util.HtmlUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
@@ -31,6 +32,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
@@ -195,7 +197,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
String loginPageHtml = generateLoginPageHtml(request, loginError,
logoutSuccess);
response.setContentType("text/html;charset=UTF-8");
response.setContentLength(loginPageHtml.length());
response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
response.getWriter().write(loginPageHtml);
return;
@@ -285,7 +287,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
for (Map.Entry<String, String> clientAuthenticationUrlToClientName : oauth2AuthenticationUrlToClientName.entrySet()) {
sb.append(" <tr><td>");
sb.append("<a href=\"").append(request.getContextPath()).append(clientAuthenticationUrlToClientName.getKey()).append("\">");
sb.append(clientAuthenticationUrlToClientName.getValue());
sb.append(HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue(), "UTF-8"));
sb.append("</a>");
sb.append("</td></tr>\n");
}
@@ -37,6 +37,7 @@ import org.springframework.security.core.Authentication;
@Target({ ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Deprecated
public @interface AuthenticationPrincipal {
/**
@@ -314,7 +314,7 @@ public class StrictHttpFirewall implements HttpFirewall {
int length = uri.length();
for (int i = 0; i < length; i++) {
char c = uri.charAt(i);
if (c < '\u0021' || '\u007e' < c) {
if (c < '\u0020' || c > '\u007e') {
return false;
}
}
@@ -340,7 +340,7 @@ public class StrictHttpFirewall implements HttpFirewall {
return true;
}
if (path.indexOf("//") > 0) {
if (path.indexOf("//") > -1) {
return false;
}
@@ -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.
@@ -74,7 +74,7 @@ public final class CacheControlHeadersWriter implements HeaderWriter {
}
private static List<Header> createHeaders() {
List<Header> headers = new ArrayList<>(2);
List<Header> headers = new ArrayList<>(3);
headers.add(new Header(CACHE_CONTROL,
"no-cache, no-store, max-age=0, must-revalidate"));
headers.add(new Header(PRAGMA, "no-cache"));
@@ -25,6 +25,8 @@ import reactor.core.publisher.Mono;
import java.net.URI;
/**
* The default {@link ServerRedirectStrategy} to use.
*
* @author Rob Winch
* @since 5.0
*/
@@ -55,6 +57,10 @@ public class DefaultServerRedirectStrategy implements ServerRedirectStrategy {
return location;
}
/**
* The {@link HttpStatus} to use for the redirect.
* @param httpStatus the status to use. Cannot be null
*/
public void setHttpStatus(HttpStatus httpStatus) {
Assert.notNull(httpStatus, "httpStatus cannot be null");
this.httpStatus = httpStatus;
@@ -29,6 +29,9 @@ import java.util.Arrays;
import java.util.List;
/**
* A {@link ServerAuthenticationEntryPoint} which delegates to multiple {@link ServerAuthenticationEntryPoint} based
* on a {@link ServerWebExchangeMatcher}
*
* @author Rob Winch
* @since 5.0
*/
@@ -26,6 +26,9 @@ import reactor.core.publisher.Mono;
import java.util.List;
/**
* A {@link SecurityWebFilterChain} that leverages a {@link ServerWebExchangeMatcher} to determine which
* {@link WebFilter} to execute.
*
* @author Rob Winch
* @since 5.0
*/
@@ -21,13 +21,25 @@ import org.springframework.web.server.WebFilter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
/**
* Defines a filter chain which is capable of being matched against a {@link ServerWebExchange} in order to decide
* whether it applies to that request.
*
* @author Rob Winch
* @since 5.0
*/
public interface SecurityWebFilterChain {
/**
* Determines if this {@link SecurityWebFilterChain} matches the provided {@link ServerWebExchange}
* @param exchange the {@link ServerWebExchange}
* @return true if it matches, else false
*/
Mono<Boolean> matches(ServerWebExchange exchange);
/**
* The {@link WebFilter} to use
* @return
*/
Flux<WebFilter> getWebFilters();
}
@@ -21,11 +21,20 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.web.server.ServerWebExchange;
/**
* Used to request authentication
*
* @author Rob Winch
* @since 5.0
*/
@FunctionalInterface
public interface ServerAuthenticationEntryPoint {
/**
* Initiates the authentication flow
*
* @param exchange
* @param e
* @return {@code Mono<Void>} to indicate when the request for authentication is complete
*/
Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e);
}
@@ -27,6 +27,7 @@ import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* Converts from a {@link ServerWebExchange} to an {@link Authentication} that can be authenticated.
*
* @author Rob Winch
* @since 5.0
@@ -23,10 +23,19 @@ import reactor.core.publisher.Mono;
import org.springframework.web.server.ServerWebExchange;
/**
* A strategy for performing redirects.
*
* @author Rob Winch
* @since 5.0
*/
@FunctionalInterface
public interface ServerRedirectStrategy {
/**
* Performs a redirect based upon the provided {@link ServerWebExchange} and {@link URI}
* @param exchange the {@link ServerWebExchange} to use
* @param location the location to redirect to
* @return {@code Mono<Void>} to indicate when redirect is complete
*/
Mono<Void> sendRedirect(ServerWebExchange exchange, URI location);
}
@@ -28,6 +28,8 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Used to delegate to a List of {@link SecurityWebFilterChain} instances.
*
* @author Rob Winch
* @since 5.0
*/
@@ -21,6 +21,8 @@ import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
/**
* A composite of the {@link ServerWebExchange} and the {@link WebFilterChain}. This is typically used as a value object
* for handling success and failures.
* @author Rob Winch
* @since 5.0
*/
@@ -36,6 +36,27 @@ import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
/**
* A {@link WebFilter} that performs authentication of a particular request. An outline of the logic:
*
* <ul>
* <li>
* A request comes in and if it does not match {@link #setRequiresAuthenticationMatcher(ServerWebExchangeMatcher)},
* then this filter does nothing and the {@link WebFilterChain} is continued. If it does match then...
* </li>
* <li>
* An attempt to convert the {@link ServerWebExchange} into an {@link Authentication} is made. If the result is
* empty, then the filter does nothing more and the {@link WebFilterChain} is continued. If it does create an
* {@link Authentication}...
* </li>
* <li>
* The {@link ReactiveAuthenticationManager} specified in
* {@link #AuthenticationWebFilter(ReactiveAuthenticationManager)} is used to perform authentication.
* </li>
* <li>
* If authentication is successful, {@link ServerAuthenticationSuccessHandler} is invoked and the authentication
* is set on {@link ReactiveSecurityContextHolder}, else {@link ServerAuthenticationFailureHandler} is invoked
* </li>
* </ul>
*
* @author Rob Winch
* @since 5.0
@@ -54,6 +75,10 @@ public class AuthenticationWebFilter implements WebFilter {
private ServerWebExchangeMatcher requiresAuthenticationMatcher = ServerWebExchangeMatchers.anyExchange();
/**
* Creates an instance
* @param authenticationManager the authentication manager to use
*/
public AuthenticationWebFilter(ReactiveAuthenticationManager authenticationManager) {
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
this.authenticationManager = authenticationManager;
@@ -87,26 +112,53 @@ public class AuthenticationWebFilter implements WebFilter {
.subscriberContext(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext)));
}
/**
* Sets the repository for persisting the SecurityContext. Default is {@link NoOpServerSecurityContextRepository}
* @param securityContextRepository the repository to use
*/
public void setSecurityContextRepository(
ServerSecurityContextRepository securityContextRepository) {
Assert.notNull(securityContextRepository, "securityContextRepository cannot be null");
this.securityContextRepository = securityContextRepository;
}
/**
* Sets the authentication success handler. Default is {@link WebFilterChainServerAuthenticationSuccessHandler}
* @param authenticationSuccessHandler the success handler to use
*/
public void setAuthenticationSuccessHandler(ServerAuthenticationSuccessHandler authenticationSuccessHandler) {
Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null");
this.authenticationSuccessHandler = authenticationSuccessHandler;
}
/**
* Sets the strategy used for converting from a {@link ServerWebExchange} to an {@link Authentication} used for
* authenticating with the provided {@link ReactiveAuthenticationManager}. If the result is empty, then it signals
* that no authentication attempt should be made. The default converter is
* {@link ServerHttpBasicAuthenticationConverter}
* @param authenticationConverter the converter to use
*/
public void setAuthenticationConverter(Function<ServerWebExchange, Mono<Authentication>> authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;
}
/**
* Sets the failure handler used when authentication fails. The default is to prompt for basic authentication.
* @param authenticationFailureHandler the handler to use. Cannot be null.
*/
public void setAuthenticationFailureHandler(
ServerAuthenticationFailureHandler authenticationFailureHandler) {
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
this.authenticationFailureHandler = authenticationFailureHandler;
}
/**
* Sets the matcher used to determine when creating an {@link Authentication} from
* {@link #setAuthenticationConverter(Function)} to be authentication. If the converter returns an empty
* result, then no authentication is attempted. The default is any request
* @param requiresAuthenticationMatcher the matcher to use. Cannot be null.
*/
public void setRequiresAuthenticationMatcher(
ServerWebExchangeMatcher requiresAuthenticationMatcher) {
Assert.notNull(requiresAuthenticationMatcher, "requiresAuthenticationMatcher cannot be null");
@@ -25,6 +25,7 @@ import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* Prompts a user for HTTP Basic authentication.
*
* @author Rob Winch
* @since 5.0
@@ -43,11 +43,19 @@ public class RedirectServerAuthenticationEntryPoint
private ServerRequestCache requestCache = new WebSessionServerRequestCache();
/**
* Creates an instance
* @param location the location to redirect to (i.e. "/logout-success")
*/
public RedirectServerAuthenticationEntryPoint(String location) {
Assert.notNull(location, "location cannot be null");
this.location = URI.create(location);
}
/**
* The request cache to use to save the request before sending a redirect.
* @param requestCache the cache to redirect to.
*/
public void setRequestCache(ServerRequestCache requestCache) {
Assert.notNull(requestCache, "requestCache cannot be null");
this.requestCache = requestCache;
@@ -37,6 +37,10 @@ public class RedirectServerAuthenticationFailureHandler
private ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();
/**
* Creates an instance
* @param location the location to redirect to (i.e. "/login?failed")
*/
public RedirectServerAuthenticationFailureHandler(String location) {
Assert.notNull(location, "location cannot be null");
this.location = URI.create(location);

Some files were not shown because too many files have changed in this diff Show More