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

Compare commits

...

128 Commits

Author SHA1 Message Date
Joe Grandja 5fde56d84a Release 5.0.8.RELEASE 2018-09-10 10:01:43 -04:00
Joe Grandja 10d53be70b Update to Spring Data Kay-SR10
Fixes gh-5808
2018-09-10 09:30:11 -04:00
Joe Grandja 93e0ce5236 Update to Reactor Bismuth-SR11
Fixes gh-5807
2018-09-07 14:57:36 -04:00
Joe Grandja 677a9634b8 Update to Spring 5.0.9
Fixes gh-5806
2018-09-07 14:57:36 -04:00
Joe Grandja 6d72c3f3aa Update to javax.servlet.jsp-api:2.3.3
Fixes gh-5805
2018-09-07 14:57:36 -04:00
Joe Grandja 67fcd51d59 Update to hibernate-validator:6.0.12.Final
Fixes gh-5804
2018-09-07 14:57:36 -04:00
Joe Grandja 15a777d534 Update to oauth2-oidc-sdk:5.64.4
Fixes gh-5803
2018-09-07 14:57:36 -04:00
Joe Grandja 7e0622c7be Update to unboundid-ldapsdk:4.0.7
Fixes gh-5802
2018-09-07 14:57:36 -04:00
Joe Grandja 07dd883d6d Update to javax.persistence:2.2.1
Fixes gh-5801
2018-09-07 14:57:36 -04:00
Rob Winch 8170e47ff2 Fix OptimizeAntPathRequestMatcher
Previously the logic for determining if the pathInfo should be appended
was inverted.

This correctly concatenates url + pathInfo if url is a non empty String.

Fixes: gh-5473
2018-09-07 11:51:50 -06:00
Christoph Dreis c408fb7edf Optimize AntPathRequestMatcher.getRequestPath() 2018-09-07 11:51:29 -06:00
Josh Cummings 4ff0fdfff9 Polish Commit on Reactive Http Basic Test 2018-09-07 11:39:54 -06:00
Tim Koopman 47b82ccd83 Reactive HttpBasic Support For Coloned Passwords
This makes so that reactive httpBasic supports passwords containing
one or more colons.
2018-09-07 11:39:41 -06:00
Rob Winch 54cc2e4509 Fix settings.gradle on Windows
Fixes: gh-5724
2018-09-07 11:37:19 -06:00
Rob Winch 31650d7eaa Ensure NamingException.resolvedObj is Serializable
Fixes: gh-5378
2018-09-04 11:06:37 -05:00
Johnny Lim 5888a3f6b7 Fix typo
Closes #5579
2018-08-03 09:58:40 -05:00
Rob Winch 87a5c16b53 Add @Configuration to ServerHttpSecurityConfiguration
Fixes: gh-5635
2018-08-03 09:39:17 -05:00
Rob Winch 4b407189c2 ServerHttpBasicAuthenticationConverter Validates Scheme Name
Fixes: gh-5615
2018-07-31 09:12:39 -05:00
Rob Winch 96c944c4fc BasicAuthenticationFilter case insenstive
Fixes: gh-5616
2018-07-31 09:11:54 -05:00
Joe Grandja 1bbae835a4 ClaimAccessor.getClaimAsString() checks null claim value
Fixes gh-5608
2018-07-30 15:41:46 -04:00
Josh Cummings de640a10d6 Next Development Version 2018-07-26 16:02:51 -06:00
Josh Cummings 85c8d53b36 Release 5.0.7.RELEASE 2018-07-26 14:46:31 -06:00
Josh Cummings 2d6bcf2cc3 Update to Spring Data Kay SR8
Fixes: gh-5591
2018-07-26 14:43:54 -06:00
Josh Cummings 18b98fc607 Update to Spring Framework 5.0.8
Fixes: gh-5590
2018-07-26 14:42:39 -06:00
Joe Grandja 7bf8091e22 Update to hibernate-validator:6.0.11.Final
Fixes gh-5576
2018-07-24 17:26:17 -04:00
Joe Grandja d19d03dea8 Update to bcpkix-jdk15on:1.60
Fixes gh-5574
2018-07-24 17:26:16 -04:00
Joe Grandja 35df3ff89b Update to org.apache.httpcomponents:httpclient:4.5.6
Fixes gh-5573
2018-07-24 17:26:16 -04:00
Joe Grandja 5f75e137d8 Update to ehcache:2.10.5
Fixes gh-5572
2018-07-24 17:26:16 -04:00
Joe Grandja b988f656c6 Update to javax.servlet.jsp.jstl-api:1.2.2
Fixes gh-5571
2018-07-24 17:26:16 -04:00
Joe Grandja 06fff9441e Update to oauth2-oidc-sdk:5.64.2
Fixes gh-5569
2018-07-24 17:26:16 -04:00
Joe Grandja edb354a2a9 Update to nimbus-jose-jwt:5.14
Fixes gh-5568
2018-07-24 17:26:16 -04:00
Joe Grandja 1ce0cf44a8 Update to cglib-nodep:3.2.7
Fixes gh-5567
2018-07-24 17:26:16 -04:00
Josh Cummings ffd8f7c8e4 Close Nimbus Information Leak
This commit captures and remaps the exception that Nimbus throws
when a PlainJWT is presented to it.

While the surrounding classes are likely only used today by the
oauth2Login flow, since they are public, we'll patch them at this
point for anyone who may be using them directly.

Fixes: gh-5457
2018-07-24 13:24:34 -06:00
Rob Winch ecaa2c5b1c Cache Control disabled for 304
Fixes: gh-5534
2018-07-17 22:13:52 -05:00
Rob Winch a1c381c276 Add JdbcUserDetailsManager(DataSource) constructor
Fixes: gh-5512
2018-07-13 15:58:36 -05:00
mhyeon.lee 1b213d65d2 Enhance OAuth2AccessToken to be serializable
Change the TokenType to Serializable
so that the OAuth2AccessToken can be serialized.
(org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType)

Fixes gh-5492
2018-07-13 12:10:55 -04:00
mhyeon.lee eb897ac69c Fix oauth2login loginProcessingUrl NPE for java config
Java Config http.oauth2Login().loginProcessingUrl("url"); throws NPE.
Override loginProcessingUrl method and cached config url.
Then when the config is initialized,
it calls the super method to complete the configuration.

Fixes gh-5488
2018-07-13 11:42:43 -04:00
Rob Winch bd8180da12 Update to Spring Boot 2.0.3.RELEASE
Issue: gh-5454
2018-06-20 15:53:56 -05:00
Rob Winch 5dabff25c3 Next Development Version 2018-06-12 21:24:07 -05:00
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
149 changed files with 2793 additions and 664 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);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -124,6 +124,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
private final RedirectionEndpointConfig redirectionEndpointConfig = new RedirectionEndpointConfig();
private final UserInfoEndpointConfig userInfoEndpointConfig = new UserInfoEndpointConfig();
private String loginPage;
private String loginProcessingUrl = OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
/**
* Sets the repository of client registrations.
@@ -156,6 +157,13 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
return this;
}
@Override
public OAuth2LoginConfigurer<B> loginProcessingUrl(String loginProcessingUrl) {
Assert.hasText(loginProcessingUrl, "loginProcessingUrl cannot be empty");
this.loginProcessingUrl = loginProcessingUrl;
return this;
}
/**
* Returns the {@link AuthorizationEndpointConfig} for configuring the Authorization Server's Authorization Endpoint.
*
@@ -378,9 +386,9 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
new OAuth2LoginAuthenticationFilter(
this.getClientRegistrationRepository(),
this.getAuthorizedClientService(),
OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
this.loginProcessingUrl);
this.setAuthenticationFilter(authenticationFilter);
this.loginProcessingUrl(OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
super.loginProcessingUrl(this.loginProcessingUrl);
if (this.loginPage != null) {
super.loginPage(this.loginPage);
}
@@ -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()
@@ -18,6 +18,7 @@ package org.springframework.security.config.annotation.web.reactive;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
@@ -35,6 +36,7 @@ import static org.springframework.security.config.web.server.ServerHttpSecurity.
* @author Rob Winch
* @since 5.0
*/
@Configuration
class ServerHttpSecurityConfiguration implements WebFluxConfigurer {
private static final String BEAN_NAME_PREFIX = "org.springframework.security.config.annotation.web.reactive.HttpSecurityConfiguration.";
private static final String HTTPSECURITY_BEAN_NAME = BEAN_NAME_PREFIX + "httpSecurity";
@@ -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();
}
}
@@ -197,6 +197,34 @@ public class OAuth2LoginConfigurerTests {
assertThat(authentication.getAuthorities()).last().hasToString("ROLE_OAUTH2_USER");
}
// gh-5488
@Test
public void oauth2LoginConfigLoginProcessingUrl() throws Exception {
// setup application context
loadConfig(OAuth2LoginConfigLoginProcessingUrl.class);
// setup authorization request
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest();
this.request.setServletPath("/login/oauth2/google");
this.authorizationRequestRepository.saveAuthorizationRequest(
authorizationRequest, this.request, this.response);
// setup authentication parameters
this.request.setParameter("code", "code123");
this.request.setParameter("state", authorizationRequest.getState());
// perform test
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
// assertions
Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication();
assertThat(authentication.getAuthorities()).hasSize(1);
assertThat(authentication.getAuthorities()).first()
.isInstanceOf(OAuth2UserAuthority.class).hasToString("ROLE_USER");
}
@Test
public void oidcLogin() throws Exception {
// setup application context
@@ -365,6 +393,19 @@ public class OAuth2LoginConfigurerTests {
}
}
@EnableWebSecurity
static class OAuth2LoginConfigLoginProcessingUrl extends CommonWebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Login()
.clientRegistrationRepository(
new InMemoryClientRegistrationRepository(CLIENT_REGISTRATION))
.loginProcessingUrl("/login/oauth2/*");
super.configure(http);
}
}
private static abstract class CommonWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
@@ -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.
@@ -37,6 +37,7 @@ import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.sql.DataSource;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -122,6 +123,13 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa
private UserCache userCache = new NullUserCache();
public JdbcUserDetailsManager() {
}
public JdbcUserDetailsManager(DataSource dataSource) {
setDataSource(dataSource);
}
// ~ Methods
// ========================================================================================================
@@ -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. */
@@ -37,9 +37,10 @@ import java.util.Base64;
*
* @author Luke Taylor
* @deprecated Digest based password encoding is not considered secure. Instead use an
* adaptive one way funciton like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
* password upgrades.
* password upgrades. There are no plans to remove this support. It is deprecated to indicate
* that this is a legacy implementation and using it is considered insecure.
*/
@Deprecated
public class LdapShaPasswordEncoder implements PasswordEncoder {
@@ -71,9 +71,10 @@ import java.util.Base64;
* @author Rob winch
* @since 5.0
* @deprecated Digest based password encoding is not considered secure. Instead use an
* adaptive one way funciton like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
* password upgrades.
* password upgrades. There are no plans to remove this support. It is deprecated to indicate
* that this is a legacy implementation and using it is considered insecure.
*/
@Deprecated
public class Md4PasswordEncoder implements PasswordEncoder {
@@ -74,9 +74,10 @@ import java.util.Base64;
* @author Rob Winch
* @since 5.0
* @deprecated Digest based password encoding is not considered secure. Instead use an
* adaptive one way funciton like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
* password upgrades.
* password upgrades. There are no plans to remove this support. It is deprecated to indicate
* that this is a legacy implementation and using it is considered insecure.
*/
@Deprecated
public class MessageDigestPasswordEncoder implements PasswordEncoder {
@@ -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.3.RELEASE
version=5.0.8.RELEASE
+45 -45
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-SR11'
mavenBom 'org.springframework:spring-framework-bom:5.0.9.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR10'
}
dependencies {
dependency 'cglib:cglib-nodep:3.2.6'
dependency 'com.squareup.okhttp3:mockwebserver:3.9.1'
dependency 'cglib:cglib-nodep:3.2.7'
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.14'
dependency 'com.nimbusds:oauth2-oidc-sdk:5.64.4'
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.7'
dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1'
dependency 'commons-cli:commons-cli:1.4'
dependency 'commons-codec:commons-codec:1.11'
@@ -60,10 +62,11 @@ dependencyManagement {
dependency 'javax.annotation:jsr250-api:1.0'
dependency 'javax.inject:javax.inject:1'
dependency 'javax.mail:mail:1.4.7'
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1'
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.2-b02'
dependency 'javax.servlet:javax.servlet-api:4.0.0'
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.2'
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.3'
dependency 'javax.servlet:javax.servlet-api:4.0.1'
dependency 'javax.validation:validation-api:2.0.1.Final'
dependency 'javax.xml.bind:jaxb-api:2.3.0'
dependency 'junit:junit:4.12'
dependency 'ldapsdk:ldapsdk:4.1'
dependency 'net.bytebuddy:byte-buddy-agent:1.7.9'
@@ -71,14 +74,12 @@ dependencyManagement {
dependency 'net.jcip:jcip-annotations:1.0'
dependency 'net.minidev:accessors-smart:1.2'
dependency 'net.minidev:json-smart:2.3'
dependency 'net.sf.ehcache:ehcache:2.10.4'
dependency 'net.sourceforge.cssparser:cssparser:0.9.24'
dependency 'net.sourceforge.htmlunit:htmlunit-core-js:2.28'
dependency 'net.sourceforge.htmlunit:htmlunit:2.29'
dependency 'net.sourceforge.htmlunit:neko-htmlunit:2.28'
dependency 'net.sf.ehcache:ehcache:2.10.5'
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'
@@ -114,7 +115,7 @@ dependencyManagement {
dependency 'org.apache.directory.shared:shared-cursor:0.9.15'
dependency 'org.apache.directory.shared:shared-ldap-constants:0.9.15'
dependency 'org.apache.directory.shared:shared-ldap:0.9.15'
dependency 'org.apache.httpcomponents:httpclient:4.5.5'
dependency 'org.apache.httpcomponents:httpclient:4.5.6'
dependency 'org.apache.httpcomponents:httpcore:4.4.8'
dependency 'org.apache.httpcomponents:httpmime:4.5.3'
dependency 'org.apache.mina:mina-core:2.0.0-M6'
@@ -127,16 +128,16 @@ dependencyManagement {
dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:8.0.44'
dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.23'
dependency 'org.apache.tomcat:tomcat-annotations-api:8.5.23'
dependency 'org.aspectj:aspectjrt:1.8.13'
dependency 'org.aspectj:aspectjtools:1.8.13'
dependency 'org.aspectj:aspectjweaver:1.8.13'
dependency 'org.assertj:assertj-core:3.9.0'
dependency 'org.aspectj:aspectjrt:1.9.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:bcpkix-jdk15on:1.60'
dependency 'org.bouncycastle:bcprov-jdk15on:1.58'
dependency 'org.codehaus.groovy:groovy-all:2.5.0-beta-1'
dependency 'org.codehaus.groovy:groovy-json:2.5.0-beta-1'
dependency 'org.codehaus.groovy:groovy:2.5.0-beta-1'
dependency 'org.codehaus.groovy:groovy-all:2.4.14'
dependency 'org.codehaus.groovy:groovy-json:2.4.14'
dependency 'org.codehaus.groovy:groovy:2.4.14'
dependency 'org.eclipse.jdt:ecj:3.12.3'
dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.7.v20170914'
dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.7.v20170914'
@@ -149,7 +150,7 @@ dependencyManagement {
dependency 'org.eclipse.jetty:jetty-servlet:9.4.7.v20170914'
dependency 'org.eclipse.jetty:jetty-util:9.4.7.v20170914'
dependency 'org.eclipse.jetty:jetty-xml:9.4.7.v20170914'
dependency 'org.eclipse.persistence:javax.persistence:2.2.0'
dependency 'org.eclipse.persistence:javax.persistence:2.2.1'
dependency 'org.gebish:geb-ast:0.10.0'
dependency 'org.gebish:geb-core:0.10.0'
dependency 'org.gebish:geb-implicit-assertions:0.10.0'
@@ -157,24 +158,23 @@ dependencyManagement {
dependency 'org.hamcrest:hamcrest-core:1.3'
dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final'
dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final'
dependency 'org.hibernate:hibernate-core:5.2.12.Final'
dependency 'org.hibernate:hibernate-entitymanager:5.2.12.Final'
dependency 'org.hibernate:hibernate-validator:6.0.5.Final'
dependency 'org.hsqldb:hsqldb:2.4.0'
dependency 'org.hibernate:hibernate-core:5.2.17.Final'
dependency 'org.hibernate:hibernate-entitymanager:5.2.17.Final'
dependency 'org.hibernate:hibernate-validator:6.0.12.Final'
dependency 'org.hsqldb:hsqldb:2.4.1'
dependency 'org.jasig.cas.client:cas-client-core:3.5.0'
dependency 'org.javassist:javassist:3.22.0-CR2'
dependency 'org.jboss.logging:jboss-logging:3.3.1.Final'
dependency 'org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.0.1.Final'
dependency 'org.jboss:jandex:2.0.3.Final'
dependency 'org.mockito:mockito-core:2.13.0'
dependency 'org.mockito:mockito-core:2.18.3'
dependency 'org.objenesis:objenesis:2.6'
dependency 'org.openid4java:openid4java-nodeps:0.9.6'
dependency 'org.ow2.asm:asm:5.0.4'
dependency 'org.ow2.asm:asm:6.0'
dependency 'org.reactivestreams:reactive-streams:1.0.1'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.29.2'
dependency 'org.seleniumhq.selenium:selenium-api:3.8.1'
dependency 'org.seleniumhq.selenium:selenium-java:3.9.1'
dependency 'org.seleniumhq.selenium:selenium-support:3.9.1'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.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
@@ -42,6 +42,7 @@ import javax.naming.OperationNotSupportedException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.ldap.InitialLdapContext;
import java.io.Serializable;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -219,6 +220,8 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
logger.debug("Authentication for " + bindPrincipal + " failed:" + exception);
}
handleResolveObj(exception);
int subErrorCode = parseSubErrorCode(exception.getMessage());
if (subErrorCode <= 0) {
@@ -234,6 +237,14 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
}
}
private void handleResolveObj(NamingException exception) {
Object resolvedObj = exception.getResolvedObj();
boolean serializable = resolvedObj instanceof Serializable;
if (resolvedObj != null && !serializable) {
exception.setResolvedObj(null);
}
}
private int parseSubErrorCode(String message) {
Matcher m = SUB_ERROR_CODE.matcher(message);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -52,13 +53,17 @@ public interface ClaimAccessor {
}
/**
* Returns the claim value as a {@code String} or {@code null} if it does not exist.
* Returns the claim value as a {@code String} or {@code null} if it does not exist or is equal to {@code null}.
*
* @param claim the name of the claim
* @return the claim value or {@code null} if it does not exist
* @return the claim value or {@code null} if it does not exist or is equal to {@code null}
*/
default String getClaimAsString(String claim) {
return (this.containsClaim(claim) ? this.getClaims().get(claim).toString() : null);
if (!this.containsClaim(claim)) {
return null;
}
Object claimValue = this.getClaims().get(claim);
return (claimValue != null ? claimValue.toString() : null);
}
/**
@@ -81,11 +86,18 @@ public interface ClaimAccessor {
if (!this.containsClaim(claim)) {
return null;
}
try {
return Instant.ofEpochMilli(Long.valueOf(this.getClaimAsString(claim)));
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Unable to convert claim '" + claim + "' to Instant: " + ex.getMessage(), ex);
Object claimValue = this.getClaims().get(claim);
if (Long.class.isAssignableFrom(claimValue.getClass())) {
return Instant.ofEpochSecond((Long) claimValue);
}
if (Date.class.isAssignableFrom(claimValue.getClass())) {
return ((Date) claimValue).toInstant();
}
if (Instant.class.isAssignableFrom(claimValue.getClass())) {
return (Instant) claimValue;
}
throw new IllegalArgumentException("Unable to convert claim '" + claim +
"' of type '" + claimValue.getClass() + "' to Instant.");
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,10 @@
*/
package org.springframework.security.oauth2.core;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.time.Instant;
import java.util.Collections;
import java.util.Set;
@@ -90,7 +92,8 @@ public class OAuth2AccessToken extends AbstractOAuth2Token {
*
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-7.1">Section 7.1 Access Token Types</a>
*/
public static final class TokenType {
public static final class TokenType implements Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
public static final TokenType BEARER = new TokenType("Bearer");
private final String value;
@@ -0,0 +1,82 @@
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
import org.junit.Before;
import org.junit.Test;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ClaimAccessor}.
*
* @author Joe Grandja
*/
public class ClaimAccessorTests {
private Map<String, Object> claims = new HashMap<>();
private ClaimAccessor claimAccessor = (() -> this.claims);
@Before
public void setup() {
this.claims.clear();
}
// gh-5192
@Test
public void getClaimAsInstantWhenDateTypeThenReturnInstant() {
Instant expectedClaimValue = Instant.now();
String claimName = "date";
this.claims.put(claimName, Date.from(expectedClaimValue));
assertThat(this.claimAccessor.getClaimAsInstant(claimName)).isBetween(
expectedClaimValue.minusSeconds(1), expectedClaimValue.plusSeconds(1));
}
// gh-5191
@Test
public void getClaimAsInstantWhenLongTypeSecondsThenReturnInstant() {
Instant expectedClaimValue = Instant.now();
String claimName = "longSeconds";
this.claims.put(claimName, expectedClaimValue.getEpochSecond());
assertThat(this.claimAccessor.getClaimAsInstant(claimName)).isBetween(
expectedClaimValue.minusSeconds(1), expectedClaimValue.plusSeconds(1));
}
@Test
public void getClaimAsInstantWhenInstantTypeThenReturnInstant() {
Instant expectedClaimValue = Instant.now();
String claimName = "instant";
this.claims.put(claimName, expectedClaimValue);
assertThat(this.claimAccessor.getClaimAsInstant(claimName)).isBetween(
expectedClaimValue.minusSeconds(1), expectedClaimValue.plusSeconds(1));
}
// gh-5608
@Test
public void getClaimAsStringWhenValueIsNullThenReturnNull() {
String claimName = "claim-with-null-value";
this.claims.put(claimName, null);
assertThat(this.claimAccessor.getClaimAsString(claimName)).isEqualTo(null);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.security.oauth2.core;
import org.junit.Test;
import org.springframework.util.SerializationUtils;
import java.time.Instant;
import java.util.Arrays;
@@ -51,16 +52,6 @@ public class OAuth2AccessTokenTests {
new OAuth2AccessToken(TOKEN_TYPE, null, ISSUED_AT, EXPIRES_AT);
}
@Test(expected = IllegalArgumentException.class)
public void constructorWhenIssuedAtIsNullThenThrowIllegalArgumentException() {
new OAuth2AccessToken(TOKEN_TYPE, TOKEN_VALUE, null, EXPIRES_AT);
}
@Test(expected = IllegalArgumentException.class)
public void constructorWhenExpiresAtIsNullThenThrowIllegalArgumentException() {
new OAuth2AccessToken(TOKEN_TYPE, TOKEN_VALUE, ISSUED_AT, null);
}
@Test(expected = IllegalArgumentException.class)
public void constructorWhenIssuedAtAfterExpiresAtThenThrowIllegalArgumentException() {
new OAuth2AccessToken(TOKEN_TYPE, TOKEN_VALUE, Instant.from(EXPIRES_AT).plusSeconds(1), EXPIRES_AT);
@@ -82,4 +73,20 @@ public class OAuth2AccessTokenTests {
assertThat(accessToken.getExpiresAt()).isEqualTo(EXPIRES_AT);
assertThat(accessToken.getScopes()).isEqualTo(SCOPES);
}
// gh-5492
@Test
public void constructorWhenCreatedThenIsSerializableAndDeserializable() {
OAuth2AccessToken accessToken = new OAuth2AccessToken(
TOKEN_TYPE, TOKEN_VALUE, ISSUED_AT, EXPIRES_AT, SCOPES);
byte[] serialized = SerializationUtils.serialize(accessToken);
accessToken = (OAuth2AccessToken) SerializationUtils.deserialize(serialized);
assertThat(serialized).isNotNull();
assertThat(accessToken.getTokenType()).isEqualTo(TOKEN_TYPE);
assertThat(accessToken.getTokenValue()).isEqualTo(TOKEN_VALUE);
assertThat(accessToken.getIssuedAt()).isEqualTo(ISSUED_AT);
assertThat(accessToken.getExpiresAt()).isEqualTo(EXPIRES_AT);
assertThat(accessToken.getScopes()).isEqualTo(SCOPES);
}
}
@@ -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
}
@@ -26,6 +26,7 @@ import com.nimbusds.jose.util.ResourceRetriever;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.JWTParser;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
@@ -95,20 +96,38 @@ public final class NimbusJwtDecoderJwkSupport implements JwtDecoder {
@Override
public Jwt decode(String token) throws JwtException {
JWT jwt = this.parse(token);
if ( jwt instanceof SignedJWT ) {
return this.createJwt(token, jwt);
}
throw new JwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
}
private JWT parse(String token) {
try {
return JWTParser.parse(token);
} catch (Exception ex) {
throw new JwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
}
}
private Jwt createJwt(String token, JWT parsedJwt) {
Jwt jwt;
try {
JWT parsedJwt = JWTParser.parse(token);
// Verify the signature
JWTClaimsSet jwtClaimsSet = this.jwtProcessor.process(parsedJwt, null);
Instant expiresAt = jwtClaimsSet.getExpirationTime().toInstant();
Instant issuedAt;
Instant expiresAt = null;
if (jwtClaimsSet.getExpirationTime() != null) {
expiresAt = jwtClaimsSet.getExpirationTime().toInstant();
}
Instant issuedAt = null;
if (jwtClaimsSet.getIssueTime() != null) {
issuedAt = jwtClaimsSet.getIssueTime().toInstant();
} else {
// issuedAt is required in AbstractOAuth2Token so let's default to expiresAt - 1 second
} else if (expiresAt != null) {
// Default to expiresAt - 1 second
issuedAt = Instant.from(expiresAt).minusSeconds(1);
}
@@ -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,95 @@
*/
package org.springframework.security.oauth2.jwt;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.JWTParser;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;
import static org.powermock.api.mockito.PowerMockito.whenNew;
/**
* Tests for {@link NimbusJwtDecoderJwkSupport}.
*
* @author Joe Grandja
* @author Josh Cummings
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({NimbusJwtDecoderJwkSupport.class, JWTParser.class})
public class NimbusJwtDecoderJwkSupportTests {
private static final String JWK_SET_URL = "https://provider.com/oauth2/keys";
private static final String JWS_ALGORITHM = JwsAlgorithms.RS256;
@Test(expected = IllegalArgumentException.class)
private String unsignedToken = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOi0yMDMzMjI0OTcsImp0aSI6IjEyMyIsInR5cCI6IkpXVCJ9.";
@Test
public void constructorWhenJwkSetUrlIsNullThenThrowIllegalArgumentException() {
new NimbusJwtDecoderJwkSupport(null);
assertThatThrownBy(() -> new NimbusJwtDecoderJwkSupport(null))
.isInstanceOf(IllegalArgumentException.class);
}
@Test(expected = IllegalArgumentException.class)
@Test
public void constructorWhenJwkSetUrlInvalidThenThrowIllegalArgumentException() {
new NimbusJwtDecoderJwkSupport("invalid.com");
assertThatThrownBy(() -> new NimbusJwtDecoderJwkSupport("invalid.com"))
.isInstanceOf(IllegalArgumentException.class);
}
@Test(expected = IllegalArgumentException.class)
@Test
public void constructorWhenJwsAlgorithmIsNullThenThrowIllegalArgumentException() {
new NimbusJwtDecoderJwkSupport(JWK_SET_URL, null);
assertThatThrownBy(() -> new NimbusJwtDecoderJwkSupport(JWK_SET_URL, null))
.isInstanceOf(IllegalArgumentException.class);
}
@Test(expected = JwtException.class)
@Test
public void decodeWhenJwtInvalidThenThrowJwtException() {
NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(JWK_SET_URL, JWS_ALGORITHM);
jwtDecoder.decode("invalid");
assertThatThrownBy(() -> jwtDecoder.decode("invalid"))
.isInstanceOf(JwtException.class);
}
// gh-5168
@Test
public void decodeWhenExpClaimNullThenDoesNotThrowException() throws Exception {
SignedJWT jwt = mock(SignedJWT.class);
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.parse(JWS_ALGORITHM)).build();
when(jwt.getHeader()).thenReturn(header);
mockStatic(JWTParser.class);
when(JWTParser.parse(anyString())).thenReturn(jwt);
DefaultJWTProcessor jwtProcessor = mock(DefaultJWTProcessor.class);
whenNew(DefaultJWTProcessor.class).withAnyArguments().thenReturn(jwtProcessor);
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().audience("resource1").build();
when(jwtProcessor.process(any(JWT.class), eq(null))).thenReturn(jwtClaimsSet);
NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(JWK_SET_URL, JWS_ALGORITHM);
assertThatCode(() -> jwtDecoder.decode("encoded-jwt")).doesNotThrowAnyException();
}
// gh-5457
@Test
public void decodeWhenPlainJwtThenExceptionDoesNotMentionClass() throws Exception {
NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(JWK_SET_URL, JWS_ALGORITHM);
assertThatCode(() -> jwtDecoder.decode(this.unsignedToken))
.isInstanceOf(JwtException.class)
.hasMessageContaining("Unsupported algorithm of none");
}
}
@@ -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 -1
View File
@@ -16,7 +16,7 @@ buildFiles.each { File buildFile ->
boolean isDefaultName = 'build.gradle'.equals(buildFile.name)
if(isDefaultName) {
String buildFilePath = buildFile.parentFile.absolutePath
String projectPath = buildFilePath.replace(rootDirPath, '').replaceAll(File.separator, ':')
String projectPath = buildFilePath.replace(rootDirPath, '').replace(File.separator, ':')
include projectPath
} else {
String projectName = buildFile.name.replace('.gradle', '');
+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");
}
@@ -154,7 +154,7 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Basic ")) {
if (header == null || !header.toLowerCase().startsWith("basic ")) {
chain.doFilter(request, response);
return;
}
@@ -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.
@@ -22,6 +22,7 @@ import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.header.Header;
import org.springframework.security.web.header.HeaderWriter;
import org.springframework.util.ReflectionUtils;
@@ -59,7 +60,7 @@ public final class CacheControlHeadersWriter implements HeaderWriter {
@Override
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
if (hasHeader(response, CACHE_CONTROL) || hasHeader(response, EXPIRES)
|| hasHeader(response, PRAGMA)) {
|| hasHeader(response, PRAGMA) || response.getStatus() == HttpStatus.NOT_MODIFIED.value()) {
return;
}
this.delegate.writeHeaders(request, response);
@@ -74,7 +75,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);
}

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