1
0
mirror of synced 2026-07-08 20:30:04 +00:00

Compare commits

...

92 Commits

Author SHA1 Message Date
Josh Cummings 3c95c08106 Release Spring Security 5.1.4.RELEASE 2019-02-13 10:36:08 -07:00
Joe Grandja 091cbe5458 ServletOAuth2AuthorizedClientExchangeFilterFunction supports chaining
Fixes gh-6483
2019-02-13 12:17:05 -05:00
Josh Cummings 04f8a7974e Update to org.jasig.cas.client:cas-client-core 3.5.1
Fixes: gh-6537
2019-02-13 10:13:49 -07:00
Josh Cummings 66f30cf606 Update to org.bouncycastle:bcpkix-jdk15on 1.61
Fixes: gh-6536
2019-02-13 10:13:49 -07:00
Josh Cummings e23e738cfe Update to httpclient 4.5.7
Fixes: gh-6534
2019-02-13 09:41:18 -07:00
Josh Cummings 54e7aace6d Update to Spring Boot 2.1.2
Fixes: gh-6533
2019-02-13 09:23:09 -07:00
Josh Cummings 4c1fdbf290 Update to appengine 1.9.71
Fixes: gh-6532
2019-02-13 09:19:40 -07:00
Josh Cummings 759c378eeb Update to Spring Reactor Californium-SR5
Fixes: gh-6531
2019-02-13 09:11:44 -07:00
Josh Cummings 9f514753d5 Update to Spring Data Lovelace-SR5
Fixes: gh-6529
2019-02-13 09:11:01 -07:00
Josh Cummings 16d5566cd6 Update to Spring Framework 5.1.5
Fixes: gh-6528
2019-02-13 09:10:22 -07:00
Stephen Doxsee 7633fbfd8a Client OAuth2 properties to use scope not scopes
OAuth2ClientProperties.Registration (which captures .properties and
.yml for OAuth2 Client) has a member `scope` but not `scopes`. Samples
and documentation were using `scopes` and have now been updated to use
`scope`.

Fixes gh-6510
2019-02-08 12:02:04 -05:00
Fabien Arrault 51f905cb89 Preserve existing refresh token if new refresh token not returned
During an oauth2 refresh if the authorization server doesn't return a new refresh token, preserve the existing one.

Fixes: gh-6503
2019-02-07 15:40:44 -05:00
Bryan Kelly 007ee7652b Missing spring: prefix on jwk-set-uri example 2019-01-25 08:31:41 -06:00
Ankur Pathak 0aaaaf9f2f Improve CsrfBeanDefinitionParser xml parsing
1. CsrfBeanDefinitionParser registers requestDataValueProcessor
if not already registered
2. Created Tests in CsrfBeanDefinitionParserTests

Fixes: gh-6423
2019-01-22 14:02:30 -06:00
Ankur Pathak be7d2a3c41 HTML markup fixed in DefaultLoginPageGeneratingFilter
Ending div moved  out of condition.

Fixes: gh-6417
2019-01-22 13:26:15 -06:00
Josh Cummings e6aa141260 authorization_uri Uses UriComponentsBuilder
Because of this, authorization_uri can now be a fully-qualified url.

Fixes: gh-5760
2019-01-19 07:51:03 -05:00
Andy Wilkinson e6bb9bfdca Fix formatting in Implicit OAuth2AuthorizedClient section 2019-01-18 10:27:54 -07:00
Denis Washington 548dc4496c Save query parameters in WebSessionServerRequestCache
Previously, URL query parameters were lost when saving a request
in WebSessionServerRequestCache. Now it is properly saved and
restored.

Fixes: gh-6421
2019-01-15 13:46:09 -06:00
Mohammad Sadeq Dousti 7f2f12c428 hasRole should not be called on a string with "ROLE_" prefix (#6353)
Removed "ROLE_" from UrlAuthorizationConfigurer

This fixes IllegalArgumentException: ROLE_ANONYMOUS should not start
with ROLE_ since ROLE_
2019-01-15 09:00:29 -06:00
Josh Cummings 6dfad970d4 Next Development Version 2019-01-10 16:54:39 -07:00
Josh Cummings 114e00f1dd Release 5.1.3.RELEASE 2019-01-10 15:40:02 -07:00
Joe Grandja a0e541eb70 Update to Spring Boot 2.1.1.RELEASE
Fixes gh-6405
2019-01-10 14:55:31 -05:00
Joe Grandja 477a5a7cd3 Update to org.powermock 2.0.0
Fixes gh-6404
2019-01-10 14:52:57 -05:00
Joe Grandja 8918a7e693 Update to ehcache 2.10.6
Fixes gh-6403
2019-01-10 14:50:11 -05:00
Joe Grandja bf33f9acef Update to com.squareup.okhttp3 3.12.1
Fixes gh-6402
2019-01-10 14:48:30 -05:00
Joe Grandja f1598b985c Update to com.fasterxml.jackson.core 2.9.8
Fixes gh-6401
2019-01-10 14:44:56 -05:00
Joe Grandja cc1ab2c368 Update to cglib-nodep 3.2.10
Fixes gh-6400
2019-01-10 14:42:47 -05:00
Joe Grandja aac4525737 Update to spring-build-conventions:0.0.23.RELEASE
Fixes gh-6389
2019-01-10 12:28:48 -05:00
Joe Grandja eeb099a04f Update to Reactor Californium-SR4
Fixes gh-6387
2019-01-10 12:05:35 -05:00
Joe Grandja 3e90c7663f Update to Spring Framework 5.1.4
Fixes gh-6386
2019-01-10 12:03:24 -05:00
Joe Grandja cfec514082 Update to Spring Data Lovelace-SR4
Fixes gh-6385
2019-01-10 12:01:24 -05:00
Ankur Pathak 80b123bf36 fixes setting paramName only when it is not null
Fixes: gh-6223
2019-01-10 10:26:49 -06:00
Rafael Renan Pacheco ee6f9e0b92 cconfigured -> configured 2019-01-08 13:19:08 -06:00
Ankur Pathak 2625de9c23 Fixes typo in x,rnc files
1. Fixes type ammount to amount in *.rnc files
2. Regenerates *.xsd files from *.rnc files

Fixes: gh-6325
2019-01-08 11:33:46 -07:00
Onur Kagan Ozcan a145653030 Fix UsernamePasswordAuthenticationTokenDeserializer to handle customized object mapper inclusion settings
Resolves #4698
2019-01-07 14:23:53 -06:00
Slava Semushin 8bc0ef86da LazyCsrfTokenRepository: fix a typo in javadoc. 2019-01-07 13:36:17 -06:00
Joe Grandja a02d6ad2a4 AuthenticationFailureEvent should publish once
Fixes gh-6281
2018-12-21 07:55:24 -05:00
Josh Cummings acdd9ba1db Split Branches Into Multiple Sonarqube Projects
Eliminate analysis collisions and simplify application of analysis
quite periods.

Fixes: gh-6091
2018-12-18 14:42:45 -07:00
finke-ba e218ac2e40 Add conditionally servlet based support for spring security web jackson module. 2018-12-18 14:22:48 -06:00
Rob Winch dd859c4a6e Fix LoginPageGeneratingWebFilter Markup
Fixes: gh-6295
2018-12-17 11:18:40 -06:00
Ian He 5d85a1c9d8 Fix DefaultLoginPageGeneratingFilter Markup
the `</h3>` should be `</h2>`.
2018-12-17 10:50:23 -06:00
Zhanwei Wang 49167458d2 Improve error message for Chinese. 2018-12-06 11:59:35 -06:00
Robbie Martinus 6b4edacde2 SessionRegistryImpl uses computeIfAbsent
Fixes: gh-5834
2018-12-05 10:28:30 -06:00
lmagyar b7cebee89e SecurityContextCallableProcessingInterceptor thread visibility fix
Within class SecurityContextCallableProcessingInterceptor field securityContext should volatile.

Fixes gh-6143
2018-12-03 15:46:20 -06:00
Rafael Dominguez 01787ea168 Restored Jacoco default task dependence
This commit ensures that the jacoco plugin is applied when calling
check and test tasks.
Also remoed the clean task that prevented sonarqube using coverage data

Fixes: gh-6199
2018-11-30 14:26:36 -06:00
Rob Winch e3e9758da7 Fix Generics in ReactorContextTestExecutionListenerTests for JDK 9+
Issue: gh-6075
2018-11-30 12:08:50 -06:00
Aanuoluwapo Otitoola 46012d62af ReactorContextTestExecutionListener should use named hooks
Fixes: gh-6075
2018-11-30 11:16:47 -06:00
Rob Winch 6eb8129173 Next Snapshot 2018-11-28 08:45:53 -06:00
Rob Winch 55dd732443 Release 5.1.2.RELEASE 2018-11-28 08:44:40 -06:00
Rob Winch 45cbdc03ba Remove Thymeleaf dependency management
Fixes: gh-6161
2018-11-28 08:20:09 -06:00
Rob Winch d99c7dbfd1 Update to GAE 1.9.68
Fixes: gh-6160
2018-11-28 08:19:56 -06:00
Rob Winch d8de4d06f7 Update to Mockito 2.23.4
Fixes: gh-6159
2018-11-28 08:19:41 -06:00
Rob Winch be2f0f0fae Update to AspectJ 1.9.2
Fixes: gh-6158
2018-11-28 08:19:33 -06:00
Rob Winch 920d5ca6ad Update to unboundid 4.0.9
Fixes: gh-6157
2018-11-28 08:19:20 -06:00
Rob Winch 6dcf5a27b1 Update to Powermock 2.0.0-RC.4
Fixes: gh-6156
2018-11-28 08:19:13 -06:00
Rob Winch c2166c4207 Update to cglib-nodep:3.2.9
Fixes: gh-6155
2018-11-28 08:18:57 -06:00
Rob Winch 1542835268 Update to Spring Data Lovelace-SR3
Fixes: gh-6154
2018-11-28 08:18:40 -06:00
Rob Winch ae471e1456 Update to Spring 5.1.3
Fixes: gh-6153
2018-11-28 08:18:31 -06:00
Rob Winch 853c54eecb Update to Reactor Californium-SR3
Fixes: gh-6152
2018-11-28 08:18:18 -06:00
Joe Grandja 10f6d10f81 Update com.squareup.okhttp3 deps to 3.12.0
Fixes gh-5570
2018-11-26 12:44:30 -05:00
Rafael Dominguez 02d844d528 Enable Code Coverage Reports in Sonar
This commit ensures that jacocoTestReport task is called when running the Sonar stage.
Additionally, a variable is passed instructing Sonar where to find the test result data.

Fixes: gh-6092
2018-11-26 09:29:21 -07:00
Rafael Dominguez c0849ba891 Update to Gradle 4.10.2
Fixes gh-6106
2018-11-21 09:59:12 -06:00
Josh Cummings db145fa03f Register NullRequestCache When Disabled
Fixes: gh-6102
2018-11-20 07:39:54 -07:00
Josh Cummings 42b8b794a8 RequestCacheConfigurerTests groovy->java
Issue: gh-4939
2018-11-20 07:37:54 -07:00
dperezcabrera 8014114225 Make InMemory*ClientRegistrationRepository Consistent
The previous builders with the list argument were inconsistent with their 
respective builders of var args.
2018-11-19 11:12:37 -06:00
Rafael Dominguez a742c0c3f2 WebClientReactiveClientCredentialsTokenResponseClient.getTokenResponse expects 2xx http status code
This ensures that token response is only extracted when ClientResponse has a successful status

Fixes: gh-6089
2018-11-19 10:51:33 -06:00
Rafael Dominguez 1cec4fe3ac OAuth2AccessTokenResponseBodyExtractor supports Object values
This commit ensures the token response is parsed correctly if the values are not a String.

Fixes: gh-6087
2018-11-15 13:24:48 -06:00
Daniel Bustamante Ospina 8655caa2de Update webflux-form sample to use Built in CSRF Support
Remove the CsrfControllerAdvice class and update dependencies to add
org.thymeleaf.extras:thymeleaf-extras-springsecurity5

Issue: gh-6061
2018-11-14 17:19:06 -06:00
Dongmin Shin dc5f5f348d Fix Typo in Reference Docs
Fixes gh-6076
2018-11-14 11:37:22 -06:00
Rafael Dominguez 35e05780ea Updated Spring Boot version from 2.1.0.M4 to 2.1.0.RELEASE 2018-11-14 10:55:26 -06:00
Gunnar Hillert a461abcf32 Docs: Fix Maven Property example spring-security.version 2018-11-13 15:09:46 -06:00
Josh Cummings ba1f5f3873 Leave Issuer As String - Documentation
Update documentation that indicated the iss claim is proactively
coerced into a URL.

Issue: gh-6073
2018-11-13 12:35:08 -07:00
Josh Cummings 5478b74116 Leave Issuer As String
Since StringOrURI is a valid issuer, MappedJwtClaimSetConverter and
JwtIssuerValidator no longer assume it.

Issue: gh-6073
2018-11-13 10:43:36 -07:00
Josh Cummings dc4aa3d017 Favor URL.toExternalForm
Converts URLs to Strings before comparing them. Uses toString(),
which delegates to toExternalForm().

Fixes: gh-6073
2018-11-13 07:39:24 -07:00
Josh Cummings 2bc156ed19 Update to spring-build-conventions:0.0.22.RELEASE
Fixes: gh-6064
2018-11-12 09:31:51 -07:00
dperezcabrera 0270994340 InMemoryUserDetailsManager.updatePassword case-insenstive
Previously updatePassword was case sensitive which was
inconsistent with the rest of the class.

This commit updates updatePassword to be case insensitive.

Fixes: gh-6039
2018-11-09 11:41:44 -06:00
Erik van Paassen a557a324c0 Fix csrf:token-repository-ref XSD documentation
The documentation of the token-repository-ref attribute of the csrf
element in the schema has been updated to make clear the default
repository is lazy. Targets versions 4.2, 5.0 and 5.1.

Fixes gh-6037
2018-11-08 10:22:07 -06:00
Josh Cummings 73022059d4 Update Reactive Resource Server Docs
Resource Server documentation for both Servlet and Reactive now have a
similar feel and offer deeper exposure to common use cases.

Fixes: gh-6054
2018-11-07 12:10:33 -07:00
Josh Cummings 165cbcb723 Write Security Headers Before Servlet Include
HeaderWriterFilter wraps request dispatcher so it can write security
headers before the include occurs.

Fixes: gh-5499
2018-10-31 09:32:40 -06:00
Josh Cummings d99ecc2a7b Add JDK 11 to Jenkins
Fixes: gh-5860
2018-10-26 07:38:55 -06:00
Josh Cummings ccd2664e6c JDK 11 Compatibility
Upgraded dependencies and removed a test in the Java Config LDAP
sample which is arguably an integration test since it starts up an
LDAP container. This test also isn't JDK 11 compatible and the
remaining integration tests in the sample cover the same material.

Issue: gh-5860
2018-10-26 07:38:31 -06:00
Joe Grandja b87ff3c00f Remove charset from Accept header in UserInfo request
Fixes gh-6017
2018-10-25 14:10:41 -04:00
Joe Grandja 8971777908 Set AuthenticationEventPublisher on each AuthenticationManagerBuilder
Fixes gh-6009
2018-10-23 14:41:26 -04:00
Drummond Dawson 3d618d78ac Remove unnecessary concatenation of sql in JdbcUserDetailsManager 2018-10-19 15:30:24 -05:00
Drummond Dawson 697b8cd734 Add missing space to heading for it to render properly 2018-10-19 15:27:35 -05:00
Rob Winch cf03faf58e Update to spring-build-conventions:0.0.20.RELEASE
Fixes: gh-5998
2018-10-18 16:14:59 -05:00
Rob Winch 13159e9f88 Fail the build if deploy fails
Fixes: gh-5997
2018-10-18 16:14:59 -05:00
Brian Demers 41b7c74928 Fixing IllegalStateException message in OAuth2ResourceServerConfigurer
Updated message to include `http.oauth2ResourceServer()`
2018-10-17 15:14:49 -05:00
Josh Cummings 0a4ac4dbfc Add JDK 10 to Jenkins
Fixes: gh-5860
2018-10-17 15:06:14 -05:00
Josh Cummings 1eb56f46a5 JDK 10 Compatibility
Upgrading dependencies and reconfiguring PowerMock

Issue: gh-5860
2018-10-17 15:06:14 -05:00
Josh Cummings cfc3c25304 Update to Spring Build Conventions 0.0.19
Fixes: gh-5981
2018-10-17 15:06:14 -05:00
sunflower-seed 03a3a3b770 Update SubjectDnX509PrincipalExtractor.java
Added missing asterisk
2018-10-17 14:58:31 -05:00
122 changed files with 2057 additions and 704 deletions
+1 -1
View File
@@ -47,7 +47,7 @@ Create your topic branch to be submitted as a pull request from master. The Spri
# Use short branch names
Branches used when submitting pull requests should preferably be named according to GitHub issues, e.g. 'gh-1234' or 'gh-1234-fix-npe'. Otherwise, use succinct, lower-case, dash (-) delimited names, such as 'fix-warnings', 'fix-typo', etc. This is important, because branch names show up in the merge commits that result from accepting pull requests, and should be as expressive and concise as possible.
#Keep commits focused
# Keep commits focused
Remember each ticket should be focused on a single item of interest since the tickets are used to produce the changelog. Since each commit should be tied to a single GitHub issue, ensure that your commits are focused. For example, do not include an update to a transitive library in your commit unless the GitHub is to update the library. Reviewing your commits is essential before sending a pull request.
Vendored
+38 -1
View File
@@ -30,7 +30,11 @@ try {
checkout scm
withCredentials([string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')]) {
try {
sh "./gradlew clean sonarqube -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
if ("master" == env.BRANCH_NAME) {
sh "./gradlew sonarqube -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
} else {
sh "./gradlew sonarqube -PexcludeProjects='**/samples/**' -Dsonar.projectKey='spring-security-${env.BRANCH_NAME}' -Dsonar.projectName='spring-security-${env.BRANCH_NAME}' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
}
} catch(Exception e) {
currentBuild.result = 'FAILED: sonar'
throw e
@@ -66,6 +70,36 @@ try {
}
}
}
},
jdk10: {
stage('JDK 10') {
node {
checkout scm
try {
withEnv(["JAVA_HOME=${ tool 'jdk10' }"]) {
sh "./gradlew clean test --refresh-dependencies --no-daemon --stacktrace"
}
} catch(Exception e) {
currentBuild.result = 'FAILED: jdk10'
throw e
}
}
}
},
jdk11: {
stage('JDK 11') {
node {
checkout scm
try {
withEnv(["JAVA_HOME=${ tool 'jdk11' }"]) {
sh "./gradlew clean test --refresh-dependencies --no-daemon --stacktrace"
}
} catch(Exception e) {
currentBuild.result = 'FAILED: jdk11'
throw e
}
}
}
}
if(currentBuild.result == 'SUCCESS') {
@@ -106,6 +140,9 @@ try {
}
}
}
} catch(Exception e) {
currentBuild.result = 'FAILED: deploys'
throw e
} finally {
def buildStatus = currentBuild.result
def buildNotSuccess = !SUCCESS.equals(buildStatus)
+2 -16
View File
@@ -1,10 +1,11 @@
buildscript {
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.18.RELEASE'
classpath 'io.spring.gradle:spring-build-conventions:0.0.23.RELEASE'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
}
repositories {
maven { url 'https://repo.spring.io/plugins-snapshot' }
maven { url 'https://plugins.gradle.org/m2/' }
}
}
apply plugin: 'io.spring.convention.root'
@@ -18,23 +19,8 @@ ext.milestoneBuild = !(snapshotBuild || releaseBuild)
dependencyManagementExport.projects = subprojects.findAll { !it.name.contains('-boot') }
// Disable JaCoCo when not explicitly requested to enable caching of test
// See https://discuss.gradle.org/t/do-not-cache-if-condition-matched-jacoco-agent-configured-with-append-true-satisfied/23504
gradle.taskGraph.whenReady { graph ->
def enabled = graph.allTasks.any { it instanceof JacocoReport }
subprojects { project ->
project.plugins.withType(JacocoPlugin) {
project.tasks.withType(Test) {
jacoco.enabled = enabled
}
}
}
}
subprojects {
plugins.withType(JavaPlugin) {
project.sourceCompatibility='1.8'
}
}
@@ -200,6 +200,7 @@ public abstract class WebSecurityConfigurerAdapter implements
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
authenticationBuilder.authenticationEventPublisher(eventPublisher);
Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
@@ -25,6 +25,7 @@ import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
@@ -87,6 +88,12 @@ public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>> exte
return this;
}
@Override
public H disable() {
getBuilder().setSharedObject(RequestCache.class, new NullRequestCache());
return super.disable();
}
@Override
public void init(H http) throws Exception {
http.setSharedObject(RequestCache.class, getRequestCache(http));
@@ -344,7 +344,7 @@ public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
* @return the {@link UrlAuthorizationConfigurer} for further customization
*/
public StandardInterceptUrlRegistry anonymous() {
return hasRole("ROLE_ANONYMOUS");
return hasRole("ANONYMOUS");
}
/**
@@ -183,9 +183,9 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
if ( this.jwtConfigurer == null ) {
throw new IllegalStateException("Jwt is the only supported format for bearer tokens " +
"in Spring Security and no Jwt configuration was found. Make sure to specify " +
"a jwk set uri by doing http.oauth2().resourceServer().jwt().jwkSetUri(uri), or wire a " +
"JwtDecoder instance by doing http.oauth2().resourceServer().jwt().decoder(decoder), or " +
"expose a JwtDecoder instance as a bean and do http.oauth2().resourceServer().jwt().");
"a jwk set uri by doing http.oauth2ResourceServer().jwt().jwkSetUri(uri), or wire a " +
"JwtDecoder instance by doing http.oauth2ResourceServer().jwt().decoder(decoder), or " +
"expose a JwtDecoder instance as a bean and do http.oauth2ResourceServer().jwt().");
}
JwtDecoder decoder = this.jwtConfigurer.getJwtDecoder();
@@ -45,6 +45,7 @@ import org.springframework.util.StringUtils;
* Parser for the {@code CsrfFilter}.
*
* @author Rob Winch
* @author Ankur Pathak
* @since 3.2
*/
public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
@@ -67,11 +68,13 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
boolean webmvcPresent = ClassUtils.isPresent(DISPATCHER_SERVLET_CLASS_NAME,
getClass().getClassLoader());
if (webmvcPresent) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
CsrfRequestDataValueProcessor.class);
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(
beanDefinition, REQUEST_DATA_VALUE_PROCESSOR);
pc.registerBeanComponent(componentDefinition);
if (!pc.getRegistry().containsBeanDefinition(REQUEST_DATA_VALUE_PROCESSOR)) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
CsrfRequestDataValueProcessor.class);
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(
beanDefinition, REQUEST_DATA_VALUE_PROCESSOR);
pc.registerBeanComponent(componentDefinition);
}
}
String matcherRef = null;
@@ -739,7 +739,7 @@ hsts-options.attlist &=
## Specifies if subdomains should be included. Default true.
attribute include-subdomains {xsd:boolean}?
hsts-options.attlist &=
## Specifies the maximum ammount of time the host should be considered a Known HSTS Host. Default one year.
## Specifies the maximum amount of time the host should be considered a Known HSTS Host. Default one year.
attribute max-age-seconds {xsd:integer}?
hsts-options.attlist &=
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
@@ -2299,7 +2299,7 @@
</xs:attribute>
<xs:attribute name="max-age-seconds" type="xs:integer">
<xs:annotation>
<xs:documentation>Specifies the maximum ammount of time the host should be considered a Known HSTS Host.
<xs:documentation>Specifies the maximum amount of time the host should be considered a Known HSTS Host.
Default one year.
</xs:documentation>
</xs:annotation>
@@ -759,7 +759,7 @@ hsts-options.attlist &=
## Specifies if subdomains should be included. Default true.
attribute include-subdomains {xsd:boolean}?
hsts-options.attlist &=
## Specifies the maximum ammount of time the host should be considered a Known HSTS Host. Default one year.
## Specifies the maximum amount of time the host should be considered a Known HSTS Host. Default one year.
attribute max-age-seconds {xsd:integer}?
hsts-options.attlist &=
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
@@ -2358,7 +2358,7 @@
</xs:attribute>
<xs:attribute name="max-age-seconds" type="xs:integer">
<xs:annotation>
<xs:documentation>Specifies the maximum ammount of time the host should be considered a Known HSTS Host.
<xs:documentation>Specifies the maximum amount of time the host should be considered a Known HSTS Host.
Default one year.
</xs:documentation>
</xs:annotation>
@@ -768,7 +768,7 @@ hsts-options.attlist &=
## Specifies if subdomains should be included. Default true.
attribute include-subdomains {xsd:boolean}?
hsts-options.attlist &=
## Specifies the maximum ammount of time the host should be considered a Known HSTS Host. Default one year.
## Specifies the maximum amount of time the host should be considered a Known HSTS Host. Default one year.
attribute max-age-seconds {xsd:integer}?
hsts-options.attlist &=
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
@@ -2387,7 +2387,7 @@
</xs:attribute>
<xs:attribute name="max-age-seconds" type="xs:integer">
<xs:annotation>
<xs:documentation>Specifies the maximum ammount of time the host should be considered a Known HSTS Host.
<xs:documentation>Specifies the maximum amount of time the host should be considered a Known HSTS Host.
Default one year.
</xs:documentation>
</xs:annotation>
@@ -748,7 +748,7 @@ csrf-options.attlist &=
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
attribute request-matcher-ref { xsd:token }?
csrf-options.attlist &=
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by LazyCsrfTokenRepository.
attribute token-repository-ref { xsd:token }?
headers =
@@ -770,7 +770,7 @@ hsts-options.attlist &=
## Specifies if subdomains should be included. Default true.
attribute include-subdomains {xsd:boolean}?
hsts-options.attlist &=
## Specifies the maximum ammount of time the host should be considered a Known HSTS Host. Default one year.
## Specifies the maximum amount of time the host should be considered a Known HSTS Host. Default one year.
attribute max-age-seconds {xsd:integer}?
hsts-options.attlist &=
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
@@ -2337,7 +2337,8 @@
</xs:attribute>
<xs:attribute name="token-repository-ref" type="xs:token">
<xs:annotation>
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by
LazyCsrfTokenRepository.
</xs:documentation>
</xs:annotation>
</xs:attribute>
@@ -2401,7 +2402,7 @@
</xs:attribute>
<xs:attribute name="max-age-seconds" type="xs:integer">
<xs:annotation>
<xs:documentation>Specifies the maximum ammount of time the host should be considered a Known HSTS Host.
<xs:documentation>Specifies the maximum amount of time the host should be considered a Known HSTS Host.
Default one year.
</xs:documentation>
</xs:annotation>
@@ -738,7 +738,7 @@ csrf-options.attlist &=
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
attribute request-matcher-ref { xsd:token }?
csrf-options.attlist &=
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by LazyCsrfTokenRepository.
attribute token-repository-ref { xsd:token }?
headers =
@@ -760,7 +760,7 @@ hsts-options.attlist &=
## Specifies if subdomains should be included. Default true.
attribute include-subdomains {xsd:boolean}?
hsts-options.attlist &=
## Specifies the maximum ammount of time the host should be considered a Known HSTS Host. Default one year.
## Specifies the maximum amount of time the host should be considered a Known HSTS Host. Default one year.
attribute max-age-seconds {xsd:integer}?
hsts-options.attlist &=
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
@@ -2232,7 +2232,8 @@
</xs:attribute>
<xs:attribute name="token-repository-ref" type="xs:token">
<xs:annotation>
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by
LazyCsrfTokenRepository.
</xs:documentation>
</xs:annotation>
</xs:attribute>
@@ -2296,7 +2297,7 @@
</xs:attribute>
<xs:attribute name="max-age-seconds" type="xs:integer">
<xs:annotation>
<xs:documentation>Specifies the maximum ammount of time the host should be considered a Known HSTS Host.
<xs:documentation>Specifies the maximum amount of time the host should be considered a Known HSTS Host.
Default one year.
</xs:documentation>
</xs:annotation>
@@ -738,7 +738,7 @@ csrf-options.attlist &=
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
attribute request-matcher-ref { xsd:token }?
csrf-options.attlist &=
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by LazyCsrfTokenRepository.
attribute token-repository-ref { xsd:token }?
headers =
@@ -760,7 +760,7 @@ hsts-options.attlist &=
## Specifies if subdomains should be included. Default true.
attribute include-subdomains {xsd:boolean}?
hsts-options.attlist &=
## Specifies the maximum ammount of time the host should be considered a Known HSTS Host. Default one year.
## Specifies the maximum amount of time the host should be considered a Known HSTS Host. Default one year.
attribute max-age-seconds {xsd:integer}?
hsts-options.attlist &=
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
@@ -2232,7 +2232,8 @@
</xs:attribute>
<xs:attribute name="token-repository-ref" type="xs:token">
<xs:annotation>
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
<xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by
LazyCsrfTokenRepository.
</xs:documentation>
</xs:annotation>
</xs:attribute>
@@ -2297,7 +2298,7 @@
</xs:attribute>
<xs:attribute name="max-age-seconds" type="xs:integer">
<xs:annotation>
<xs:documentation>Specifies the maximum ammount of time the host should be considered a Known HSTS Host.
<xs:documentation>Specifies the maximum amount of time the host should be considered a Known HSTS Host.
Default one year.
</xs:documentation>
</xs:annotation>
@@ -79,6 +79,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
</body></html>"""
when: "fail to log in"
super.setup()
@@ -121,6 +122,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
</body></html>"""
when: "login success"
super.setup()
@@ -168,6 +170,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
</body></html>"""
}
@@ -272,6 +275,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
</body></html>"""
}
@@ -318,6 +322,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
</body></html>"""
}
@@ -377,6 +382,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
</body></html>"""
}
@@ -1,209 +0,0 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers
import org.springframework.security.core.userdetails.PasswordEncodedUser
import javax.servlet.http.HttpServletResponse
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.security.config.annotation.AnyObjectPostProcessor
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.web.savedrequest.RequestCache
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
import spock.lang.Unroll;
/**
*
* @author Rob Winch
*/
class RequestCacheConfigurerTests extends BaseSpringSpec {
def "requestCache ObjectPostProcessor"() {
setup:
AnyObjectPostProcessor opp = Mock()
HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:])
when:
http
.requestCache()
.and()
.build()
then: "RequestCacheAwareFilter is registered with LifecycleManager"
1 * opp.postProcess(_ as RequestCacheAwareFilter) >> {RequestCacheAwareFilter o -> o}
}
def "invoke requestCache twice does not reset"() {
setup:
RequestCache RC = Mock()
AnyObjectPostProcessor opp = Mock()
HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:])
when:
http
.requestCache()
.requestCache(RC)
.and()
.requestCache()
then:
http.getSharedObject(RequestCache) == RC
}
def "RequestCache disables faviocon.ico"() {
setup:
loadConfig(RequestCacheDefautlsConfig)
request.servletPath = "/favicon.ico"
request.requestURI = "/favicon.ico"
request.method = "GET"
when: "request favicon.ico"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the login page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
when: "authenticate successfully"
super.setupWeb(request.session)
request.servletPath = "/login"
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default URL since it was favicon.ico"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/"
}
def "RequestCache disables faviocon.png"() {
setup:
loadConfig(RequestCacheDefautlsConfig)
request.servletPath = "/favicon.png"
request.requestURI = "/favicon.png"
request.method = "GET"
when: "request favicon.ico"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the login page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
when: "authenticate successfully"
super.setupWeb(request.session)
request.servletPath = "/login"
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default URL since it was favicon.ico"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/"
}
def "SEC-2321: RequestCache disables application/json"() {
setup:
loadConfig(RequestCacheDefautlsConfig)
request.addHeader("Accept", MediaType.APPLICATION_JSON_VALUE)
request.method = "GET"
request.servletPath = "/messages"
request.requestURI = "/messages"
when: "request application/json"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the login page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
when: "authenticate successfully"
super.setupWeb(request.session)
request.servletPath = "/login"
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default URL since it was application/json. This is desirable since JSON requests are typically not invoked directly from the browser and we don't want the browser to replay them"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/"
}
def "SEC-2321: RequestCache disables X-Requested-With"() {
setup:
loadConfig(RequestCacheDefautlsConfig)
request.addHeader("X-Requested-With", "XMLHttpRequest")
request.method = "GET"
request.servletPath = "/messages"
request.requestURI = "/messages"
when: "request X-Requested-With"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the login page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/login"
when: "authenticate successfully"
super.setupWeb(request.session)
request.servletPath = "/login"
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default URL since it was X-Requested-With"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/"
}
@Unroll
def "RequestCache saves #headerName: #headerValue"() {
setup:
loadConfig(RequestCacheDefautlsConfig)
request.addHeader(headerName, headerValue)
request.method = "GET"
request.servletPath = "/messages"
request.requestURI = "/messages"
when: "request content type"
springSecurityFilterChain.doFilter(request,response,chain)
super.setupWeb(request.session)
request.servletPath = "/login"
request.setParameter("username","user")
request.setParameter("password","password")
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to saved URL"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "http://localhost/messages"
where:
headerName << ["Accept", "Accept", "Accept", "X-Requested-With"]
headerValue << [MediaType.ALL_VALUE, MediaType.TEXT_HTML, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8","com.android"]
}
@EnableWebSecurity
static class RequestCacheDefautlsConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser(PasswordEncodedUser.user());
}
}
}
@@ -15,23 +15,31 @@
*/
package org.springframework.security.config;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Matchers.*;
import static org.powermock.api.mockito.PowerMockito.*;
import org.apache.commons.logging.Log;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.messaging.Message;
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.powermock.api.mockito.PowerMockito.doThrow;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.verifyZeroInteractions;
/**
*
* @author Luke Taylor
@@ -40,6 +48,7 @@ import org.springframework.util.ClassUtils;
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ ClassUtils.class })
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*" })
public class SecurityNamespaceHandlerTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@@ -15,16 +15,15 @@
*/
package org.springframework.security.config.annotation.web;
import static org.assertj.core.api.Assertions.assertThat;
import static org.powermock.api.mockito.PowerMockito.*;
import java.util.Arrays;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -33,6 +32,10 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.when;
/**
*
* @author Rob Winch
@@ -40,6 +43,7 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ SpringFactoriesLoader.class })
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*" })
public class WebSecurityConfigurerAdapterPowermockTests {
ConfigurableWebApplicationContext context;
@@ -15,12 +15,22 @@
*/
package org.springframework.security.config.annotation.web;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
@@ -48,14 +58,6 @@ import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
import static org.mockito.ArgumentMatchers.any;
@@ -75,6 +77,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@PrepareForTest({WebAsyncManager.class})
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*", "javax.xml.transform.*" })
public class WebSecurityConfigurerAdapterTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@@ -0,0 +1,295 @@
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
/**
* Tests for {@link RequestCacheConfigurer}
*
* @author Rob Winch
* @author Josh Cummings
*/
public class RequestCacheConfigurerTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
MockMvc mvc;
@Test
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnExceptionTranslationFilter() {
this.spring.register(ObjectPostProcessorConfig.class, DefaultSecurityConfig.class).autowire();
verify(ObjectPostProcessorConfig.objectPostProcessor)
.postProcess(any(RequestCacheAwareFilter.class));
}
@EnableWebSecurity
static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter {
static ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestCache();
// @formatter:on
}
@Bean
static ObjectPostProcessor<Object> objectPostProcessor() {
return objectPostProcessor;
}
}
static class ReflectingObjectPostProcessor implements ObjectPostProcessor<Object> {
@Override
public <O> O postProcess(O object) {
return object;
}
}
@Test
public void getWhenInvokingExceptionHandlingTwiceThenOriginalEntryPointUsed() throws Exception {
this.spring.register(InvokeTwiceDoesNotOverrideConfig.class).autowire();
this.mvc.perform(get("/"));
verify(InvokeTwiceDoesNotOverrideConfig.requestCache)
.getMatchingRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@EnableWebSecurity
static class InvokeTwiceDoesNotOverrideConfig extends WebSecurityConfigurerAdapter {
static RequestCache requestCache = mock(RequestCache.class);
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestCache()
.requestCache(requestCache)
.and()
.requestCache();
// @formatter:on
}
}
@Test
public void getWhenBookmarkedUrlIsFaviconIcoThenPostAuthenticationRedirectsToRoot() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/favicon.ico"))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("/")); // ignores favicon.ico
}
@Test
public void getWhenBookmarkedUrlIsFaviconPngThenPostAuthenticationRedirectsToRoot() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/favicon.png"))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("/")); // ignores favicon.png
}
// SEC-2321
@Test
public void getWhenBookmarkedRequestIsApplicationJsonThenPostAuthenticationRedirectsToRoot() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/messages")
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("/")); // ignores application/json
// This is desirable since JSON requests are typically not invoked directly from the browser and we don't want the browser to replay them
}
// SEC-2321
@Test
public void getWhenBookmarkedRequestIsXRequestedWithThenPostAuthenticationRedirectsToRoot() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/messages")
.header("X-Requested-With", "XMLHttpRequest"))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("/"));
// This is desirable since XHR requests are typically not invoked directly from the browser and we don't want the browser to replay them
}
@Test
public void getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemembers() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/messages")
.header(HttpHeaders.ACCEPT, MediaType.ALL))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("http://localhost/messages"));
}
@Test
public void getWhenBookmarkedRequestIsTextHtmlThenPostAuthenticationRemembers() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/messages")
.header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("http://localhost/messages"));
}
@Test
public void getWhenBookmarkedRequestIsChromeThenPostAuthenticationRemembers() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/messages")
.header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("http://localhost/messages"));
}
@Test
public void getWhenBookmarkedRequestIsRequestedWithAndroidThenPostAuthenticationRemembers() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/messages")
.header("X-Requested-With", "com.android"))
.andExpect(redirectedUrl("http://localhost/login"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("http://localhost/messages"));
}
@EnableWebSecurity
static class RequestCacheDefaultsConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
// gh-6102
@Test
public void getWhenRequestCacheIsDisabledThenExceptionTranslationFilterDoesNotStoreRequest() throws Exception {
this.spring.register(RequestCacheDisabledConfig.class, DefaultSecurityConfig.class).autowire();
MockHttpSession session = (MockHttpSession)
this.mvc.perform(get("/bob"))
.andReturn().getRequest().getSession();
this.mvc.perform(formLogin(session))
.andExpect(redirectedUrl("/"));
}
@EnableWebSecurity
static class RequestCacheDisabledConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.requestCache().disable();
}
}
@EnableWebSecurity
static class DefaultSecurityConfig {
@Bean
public InMemoryUserDetailsManager userDetailsManager() {
return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
);
}
}
private static RequestBuilder formLogin(MockHttpSession session) {
return post("/login")
.param("username", "user")
.param("password", "password")
.session(session)
.with(csrf());
}
}
@@ -15,15 +15,7 @@
*/
package org.springframework.security.config.annotation.web.configurers;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.same;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.when;
import java.lang.reflect.Method;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
@@ -32,8 +24,10 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.mock.web.MockFilterChain;
@@ -52,12 +46,20 @@ import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.util.ReflectionUtils;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.same;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.when;
/**
*
* @author Rob Winch
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ ReflectionUtils.class, Method.class })
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*" })
public class SessionManagementConfigurerServlet31Tests {
@Mock
Method method;
@@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
* @author M.S. Dousti
*
*/
public class UrlAuthorizationConfigurerTests {
@@ -203,6 +204,24 @@ public class UrlAuthorizationConfigurerTests {
}
}
@Test
public void anonymousUrlAuthorization() {
loadConfig(AnonymousUrlAuthorizationConfig.class);
}
@EnableWebSecurity
@Configuration
static class AnonymousUrlAuthorizationConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.apply(new UrlAuthorizationConfigurer<>(null)).getRegistry()
.anyRequest().anonymous();
// @formatter:on
}
}
public void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
@@ -135,7 +135,10 @@ public class OAuth2ClientConfigurerTests {
MvcResult mvcResult = this.mockMvc.perform(get("/oauth2/authorization/registration-1"))
.andExpect(status().is3xxRedirection())
.andReturn();
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Fclient-1");
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" +
"response_type=code&client_id=client-1&" +
"scope=user&state=.{15,}&" +
"redirect_uri=http://localhost/client-1");
}
@Test
@@ -184,7 +187,10 @@ public class OAuth2ClientConfigurerTests {
MvcResult mvcResult = this.mockMvc.perform(get("/resource1").with(user("user1")))
.andExpect(status().is3xxRedirection())
.andReturn();
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?response_type=code&client_id=client-1&scope=user&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Fclient-1");
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" +
"response_type=code&client_id=client-1&" +
"scope=user&state=.{15,}&" +
"redirect_uri=http://localhost/client-1");
verify(requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@@ -21,6 +21,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
@@ -28,6 +29,7 @@ import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@@ -156,6 +158,30 @@ public class OAuth2LoginConfigurerTests {
.isInstanceOf(OAuth2UserAuthority.class).hasToString("ROLE_USER");
}
// gh-6009
@Test
public void oauth2LoginWhenSuccessThenAuthenticationSuccessEventPublished() throws Exception {
// setup application context
loadConfig(OAuth2LoginConfig.class);
// setup authorization request
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest();
this.authorizationRequestRepository.saveAuthorizationRequest(
authorizationRequest, this.request, this.response);
// setup authentication parameters
this.request.setParameter("code", "code123");
this.request.setParameter("state", authorizationRequest.getState());
// perform test
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
// assertions
assertThat(OAuth2LoginConfig.EVENTS).isNotEmpty();
assertThat(OAuth2LoginConfig.EVENTS).hasSize(1);
assertThat(OAuth2LoginConfig.EVENTS.get(0)).isInstanceOf(AuthenticationSuccessEvent.class);
}
@Test
public void oauth2LoginWhenAuthenticatedThenIgnored() throws Exception {
// setup application context
@@ -467,7 +493,9 @@ public class OAuth2LoginConfigurerTests {
}
@EnableWebSecurity
static class OAuth2LoginConfig extends CommonWebSecurityConfigurerAdapter {
static class OAuth2LoginConfig extends CommonWebSecurityConfigurerAdapter implements ApplicationListener<AuthenticationSuccessEvent> {
static List<AuthenticationSuccessEvent> EVENTS = new ArrayList<>();
@Override
protected void configure(HttpSecurity http) throws Exception {
http
@@ -476,6 +504,11 @@ public class OAuth2LoginConfigurerTests {
new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION));
super.configure(http);
}
@Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
EVENTS.add(event);
}
}
@EnableWebSecurity
@@ -0,0 +1,41 @@
/*
* 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.http;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author Ankur Pathak
*/
public class CsrfBeanDefinitionParserTests {
private static final String CONFIG_LOCATION_PREFIX =
"classpath:org/springframework/security/config/http/CsrfBeanDefinitionParserTests";
@Test
public void registerDataValueProcessorOnlyIfNotRegistered() throws Exception {
try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext()) {
context.setAllowBeanDefinitionOverriding(false);
context.setConfigLocation(this.xml("RegisterDataValueProcessorOnyIfNotRegistered"));
context.refresh();
}
}
private String xml(String configName) {
return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
}
}
@@ -80,6 +80,7 @@ public class FormLoginBeanDefinitionParserTests {
+ " </p>\n"
+ " <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
+ " </form>\n"
+ "</div>\n"
+ "</body></html>";
this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
@@ -126,6 +127,7 @@ public class FormLoginBeanDefinitionParserTests {
+ " </p>\n"
+ " <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
+ " </form>\n"
+ "</div>\n"
+ "</body></html>";
this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
@@ -171,6 +173,7 @@ public class FormLoginBeanDefinitionParserTests {
+ " </p>\n"
+ " <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
+ " </form>\n"
+ "</div>\n"
+ "</body></html>";
this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
@@ -214,6 +217,7 @@ public class FormLoginBeanDefinitionParserTests {
+ " </p>\n"
+ " <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
+ " </form>\n"
+ "</div>\n"
+ "</body></html>";
this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
@@ -15,15 +15,7 @@
*/
package org.springframework.security.config.http;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.same;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.when;
import java.lang.reflect.Method;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
@@ -32,14 +24,14 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextImpl;
@@ -47,12 +39,20 @@ import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.util.ReflectionUtils;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.same;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.when;
/**
* @author Rob Winch
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ ReflectionUtils.class, Method.class })
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*" })
public class SessionManagementConfigServlet31Tests {
private static final String XML_AUTHENTICATION_MANAGER = "<authentication-manager>"
+ " <authentication-provider>" + " <user-service>"
@@ -0,0 +1,31 @@
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
">
<security:http pattern="/foo/**">
<security:intercept-url pattern="/**" access="hasRole('FOO')" />
<security:http-basic/>
</security:http>
<security:http pattern="/bar/**">
<security:intercept-url pattern="/**" access="hasRole('BAR')" />
<security:http-basic/>
</security:http>
<security:authentication-manager >
<security:authentication-provider>
<security:user-service>
<security:user name="gnu" password="{noop}gnat" authorities="ROLE_FOO" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
@@ -112,7 +112,9 @@ class MethodSecurityEvaluationContext extends StandardEvaluationContext {
}
for (int i = 0; i < args.length; i++) {
super.setVariable(paramNames[i], args[i]);
if (paramNames[i] != null) {
setVariable(paramNames[i], args[i]);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authentication;
import java.util.Collections;
@@ -157,7 +156,9 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {
@@ -196,7 +197,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parent.authenticate(authentication);
result = parentResult = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
@@ -205,7 +206,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
// handled the request
}
catch (AuthenticationException e) {
lastException = e;
lastException = parentException = e;
}
}
@@ -217,7 +218,11 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
((CredentialsContainer) result).eraseCredentials();
}
eventPublisher.publishAuthenticationSuccess(result);
// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
@@ -230,7 +235,11 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
"No AuthenticationProvider found for {0}"));
}
prepareException(lastException, authentication);
// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
@@ -132,17 +132,7 @@ public class SessionRegistryImpl implements SessionRegistry,
sessionIds.put(sessionId,
new SessionInformation(principal, sessionId, new Date()));
Set<String> sessionsUsedByPrincipal = principals.get(principal);
if (sessionsUsedByPrincipal == null) {
sessionsUsedByPrincipal = new CopyOnWriteArraySet<>();
Set<String> prevSessionsUsedByPrincipal = principals.putIfAbsent(principal,
sessionsUsedByPrincipal);
if (prevSessionsUsedByPrincipal != null) {
sessionsUsedByPrincipal = prevSessionsUsedByPrincipal;
}
}
Set<String> sessionsUsedByPrincipal = principals.computeIfAbsent(principal, key -> new CopyOnWriteArraySet<>());
sessionsUsedByPrincipal.add(sessionId);
if (logger.isTraceEnabled()) {
@@ -57,6 +57,7 @@ import java.util.Set;
* mapper.registerModule(new CoreJackson2Module());
* mapper.registerModule(new CasJackson2Module());
* mapper.registerModule(new WebJackson2Module());
* mapper.registerModule(new WebServletJackson2Module());
* </pre>
*
* @author Jitendra Singh.
@@ -70,6 +71,8 @@ public final class SecurityJackson2Modules {
"org.springframework.security.cas.jackson2.CasJackson2Module",
"org.springframework.security.web.jackson2.WebJackson2Module"
);
private static final String webServletJackson2ModuleClass =
"org.springframework.security.web.jackson2.WebServletJackson2Module";
private SecurityJackson2Modules() {
}
@@ -109,14 +112,26 @@ public final class SecurityJackson2Modules {
public static List<Module> getModules(ClassLoader loader) {
List<Module> modules = new ArrayList<>();
for (String className : securityJackson2ModuleClasses) {
Module module = loadAndGetInstance(className, loader);
if (module != null) {
modules.add(module);
}
addToModulesList(loader, modules, className);
}
if (ClassUtils.isPresent("javax.servlet.http.Cookie", loader)) {
addToModulesList(loader, modules, webServletJackson2ModuleClass);
}
return modules;
}
/**
* @param loader the ClassLoader to use
* @param modules list of the modules to add
* @param className name of the class to instantiate
*/
private static void addToModulesList(ClassLoader loader, List<Module> modules, String className) {
Module module = loadAndGetInstance(className, loader);
if (module != null) {
modules.add(module);
}
}
/**
* Creates a TypeResolverBuilder that performs whitelisting.
* @return a TypeResolverBuilder that performs whitelisting.
@@ -41,6 +41,7 @@ import org.springframework.security.core.GrantedAuthority;
*
* @author Jitendra Singh
* @author Greg Turnquist
* @author Onur Kagan Ozcan
* @see UsernamePasswordAuthenticationTokenMixin
* @since 4.2
*/
@@ -69,7 +70,7 @@ class UsernamePasswordAuthenticationTokenDeserializer extends JsonDeserializer<U
}
JsonNode credentialsNode = readJsonNode(jsonNode, "credentials");
Object credentials;
if (credentialsNode.isNull()) {
if (credentialsNode.isNull() || credentialsNode.isMissingNode()) {
credentials = null;
} else {
credentials = credentialsNode.asText();
@@ -83,7 +84,7 @@ class UsernamePasswordAuthenticationTokenDeserializer extends JsonDeserializer<U
token = new UsernamePasswordAuthenticationToken(principal, credentials);
}
JsonNode detailsNode = readJsonNode(jsonNode, "details");
if (detailsNode.isNull()) {
if (detailsNode.isNull() || detailsNode.isMissingNode()) {
token.setDetails(null);
} else {
token.setDetails(detailsNode);
@@ -143,7 +143,7 @@ public class InMemoryUserDetailsManager implements UserDetailsManager,
@Override
public UserDetails updatePassword(UserDetails user, String newPassword) {
String username = user.getUsername();
MutableUserDetails mutableUser = this.users.get(username);
MutableUserDetails mutableUser = this.users.get(username.toLowerCase());
mutableUser.setPassword(newPassword);
return mutableUser;
}
@@ -76,7 +76,7 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa
// GroupManager SQL
public static final String DEF_FIND_GROUPS_SQL = "select group_name from groups";
public static final String DEF_FIND_USERS_IN_GROUP_SQL = "select username from group_members gm, groups g "
+ "where gm.group_id = g.id" + " and g.group_name = ?";
+ "where gm.group_id = g.id and g.group_name = ?";
public static final String DEF_INSERT_GROUP_SQL = "insert into groups (group_name) values (?)";
public static final String DEF_FIND_GROUP_ID_SQL = "select id from groups where group_name = ?";
public static final String DEF_INSERT_GROUP_AUTHORITY_SQL = "insert into group_authorities (group_id, authority) values (?,?)";
@@ -1,7 +1,7 @@
AbstractAccessDecisionManager.accessDenied=\u4E0D\u5141\u8BB8\u8BBF\u95EE
AbstractLdapAuthenticationProvider.emptyPassword=\u574F\u7684\u51ED\u8BC1
AbstractLdapAuthenticationProvider.emptyPassword=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
AbstractSecurityInterceptor.authenticationNotFound=\u672A\u5728SecurityContext\u4E2D\u67E5\u627E\u5230\u8BA4\u8BC1\u5BF9\u8C61
AbstractUserDetailsAuthenticationProvider.badCredentials=\u574F\u7684\u51ED\u8BC1
AbstractUserDetailsAuthenticationProvider.badCredentials=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
AbstractUserDetailsAuthenticationProvider.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
AbstractUserDetailsAuthenticationProvider.disabled=\u7528\u6237\u5DF2\u5931\u6548
AbstractUserDetailsAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
@@ -13,8 +13,8 @@ AccountStatusUserDetailsChecker.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u67
AccountStatusUserDetailsChecker.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
AclEntryAfterInvocationProvider.noPermission=\u7ED9\u5B9A\u7684Authentication\u5BF9\u8C61({0})\u6839\u672C\u65E0\u6743\u64CD\u63A7\u9886\u57DF\u5BF9\u8C61({1})
AnonymousAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684AnonymousAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
BindAuthenticator.badCredentials=\u574F\u7684\u51ED\u8BC1
BindAuthenticator.emptyPassword=\u574F\u7684\u51ED\u8BC1
BindAuthenticator.badCredentials=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
BindAuthenticator.emptyPassword=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
CasAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684CasAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
CasAuthenticationProvider.noServiceTicket=\u672A\u80FD\u591F\u6B63\u786E\u63D0\u4F9B\u5F85\u9A8C\u8BC1\u7684CAS\u670D\u52A1\u7968\u6839
ConcurrentSessionControlAuthenticationStrategy.exceededAllowed=\u5DF2\u7ECF\u8D85\u8FC7\u4E86\u5F53\u524D\u4E3B\u4F53({0})\u88AB\u5141\u8BB8\u7684\u6700\u5927\u4F1A\u8BDD\u6570\u91CF
@@ -30,14 +30,14 @@ DigestAuthenticationFilter.nonceNotTwoTokens=Nonce\u5E94\u8BE5\u7531\u4E24\u90E8
DigestAuthenticationFilter.usernameNotFound=\u7528\u6237\u540D{0}\u672A\u627E\u5230
JdbcDaoImpl.noAuthority=\u6CA1\u6709\u4E3A\u7528\u6237{0}\u6307\u5B9A\u89D2\u8272
JdbcDaoImpl.notFound=\u672A\u627E\u5230\u7528\u6237{0}
LdapAuthenticationProvider.badCredentials=\u574F\u7684\u51ED\u8BC1
LdapAuthenticationProvider.badCredentials=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
LdapAuthenticationProvider.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
LdapAuthenticationProvider.disabled=\u7528\u6237\u5DF2\u5931\u6548
LdapAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
LdapAuthenticationProvider.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
LdapAuthenticationProvider.emptyUsername=\u7528\u6237\u540D\u4E0D\u5141\u8BB8\u4E3A\u7A7A
LdapAuthenticationProvider.onlySupports=\u4EC5\u4EC5\u652F\u6301UsernamePasswordAuthenticationToken
PasswordComparisonAuthenticator.badCredentials=\u574F\u7684\u51ED\u8BC1
PasswordComparisonAuthenticator.badCredentials=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
#PersistentTokenBasedRememberMeServices.cookieStolen=Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.
ProviderManager.providerNotFound=\u672A\u67E5\u627E\u5230\u9488\u5BF9{0}\u7684AuthenticationProvider
RememberMeAuthenticationProvider.incorrectKey=\u5C55\u793ARememberMeAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
@@ -0,0 +1,76 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.access.expression.method;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.util.ReflectionUtils;
import static org.mockito.Mockito.doReturn;
/**
* @author shabarijonnalagadda
*
*/
@RunWith(MockitoJUnitRunner.class)
public class MethodSecurityEvaluationContextTests {
@Mock
private ParameterNameDiscoverer paramNameDiscoverer;
@Mock
private Authentication authentication;
@Mock
private MethodInvocation methodInvocation;
@Test
public void lookupVariableWhenParameterNameNullThenNotSet() {
Class<String> type = String.class;
Method method = ReflectionUtils.findMethod(String.class, "contains", CharSequence.class);
doReturn(new String[] {null}).when(paramNameDiscoverer).getParameterNames(method);
doReturn(new Object[]{null}).when(methodInvocation).getArguments();
doReturn(type).when(methodInvocation).getThis();
doReturn(method).when(methodInvocation).getMethod();
NotNullVariableMethodSecurityEvaluationContext context= new NotNullVariableMethodSecurityEvaluationContext(authentication, methodInvocation, paramNameDiscoverer);
context.lookupVariable("testVariable");
}
private static class NotNullVariableMethodSecurityEvaluationContext
extends MethodSecurityEvaluationContext {
public NotNullVariableMethodSecurityEvaluationContext(Authentication auth, MethodInvocation mi,
ParameterNameDiscoverer parameterNameDiscoverer) {
super(auth, mi, parameterNameDiscoverer);
}
@Override
public void setVariable(String name, @Nullable Object value) {
if ( name == null ) {
throw new IllegalArgumentException("name should not be null");
}
else {
super.setVariable(name, value);
}
}
}
}
@@ -16,18 +16,20 @@
package org.springframework.security.authentication;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.springframework.context.MessageSource;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Mockito.*;
/**
* Tests {@link ProviderManager}.
*
@@ -257,7 +259,6 @@ public class ProviderManagerTests {
catch (BadCredentialsException e) {
assertThat(e).isSameAs(expected);
}
verify(publisher).publishAuthenticationFailure(expected, authReq);
}
@Test
@@ -298,6 +299,32 @@ public class ProviderManagerTests {
}
}
// gh-6281
@Test
public void authenticateWhenFailsInParentAndPublishesThenChildDoesNotPublish() {
BadCredentialsException badCredentialsExParent = new BadCredentialsException("Bad Credentials in parent");
ProviderManager parentMgr = new ProviderManager(
Collections.singletonList(createProviderWhichThrows(badCredentialsExParent)));
ProviderManager childMgr = new ProviderManager(Collections.singletonList(createProviderWhichThrows(
new BadCredentialsException("Bad Credentials in child"))), parentMgr);
AuthenticationEventPublisher publisher = mock(AuthenticationEventPublisher.class);
parentMgr.setAuthenticationEventPublisher(publisher);
childMgr.setAuthenticationEventPublisher(publisher);
final Authentication authReq = mock(Authentication.class);
try {
childMgr.authenticate(authReq);
fail("Expected exception");
}
catch (BadCredentialsException e) {
assertThat(e).isSameAs(badCredentialsExParent);
}
verify(publisher).publishAuthenticationFailure(badCredentialsExParent, authReq); // Parent publishes
verifyNoMoreInteractions(publisher); // Child should not publish (duplicate event)
}
private AuthenticationProvider createProviderWhichThrows(
final AuthenticationException e) {
AuthenticationProvider provider = mock(AuthenticationProvider.class);
@@ -29,11 +29,16 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import static org.assertj.core.api.Assertions.*;
import static com.fasterxml.jackson.annotation.JsonInclude.Include.ALWAYS;
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_ABSENT;
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
import static com.fasterxml.jackson.annotation.JsonInclude.Value.construct;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jitendra Singh
* @author Greg Turnquist
* @author Onur Kagan Ozcan
* @since 4.2
*/
public class UsernamePasswordAuthenticationTokenMixinTests extends AbstractMixinTests {
@@ -163,6 +168,20 @@ public class UsernamePasswordAuthenticationTokenMixinTests extends AbstractMixin
assertThat(deserialized).isEqualTo(original);
}
@Test
public void serializingThenDeserializingWithConfiguredObjectMapperShouldWork() throws IOException {
// given
this.mapper.setDefaultPropertyInclusion(construct(ALWAYS, NON_NULL)).setSerializationInclusion(NON_ABSENT);
UsernamePasswordAuthenticationToken original = new UsernamePasswordAuthenticationToken("Frodo", null);
// when
String serialized = this.mapper.writeValueAsString(original);
UsernamePasswordAuthenticationToken deserialized =
this.mapper.readValue(serialized, UsernamePasswordAuthenticationToken.class);
// then
assertThat(deserialized).isEqualTo(original);
}
private UsernamePasswordAuthenticationToken createToken() {
User user = createDefaultUser();
@@ -18,6 +18,7 @@ package org.springframework.security.provisioning;
import org.junit.Test;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import static org.assertj.core.api.Assertions.*;
@@ -37,4 +38,15 @@ public class InMemoryUserDetailsManagerTests {
this.manager.updatePassword(this.user, newPassword);
assertThat(this.manager.loadUserByUsername(this.user.getUsername()).getPassword()).isEqualTo(newPassword);
}
@Test
public void changePasswordWhenUsernameIsNotInLowercase() {
UserDetails userNotLowerCase = User.withUserDetails(PasswordEncodedUser.user())
.username("User")
.build();
String newPassword = "newPassword";
this.manager.updatePassword(userNotLowerCase, newPassword);
assertThat(this.manager.loadUserByUsername(userNotLowerCase.getUsername()).getPassword()).isEqualTo(newPassword);
}
}
@@ -26,7 +26,7 @@ In order to use Spring Security you must add the necessary dependencies. For the
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId> <1>
<artifactId>thymeleaf-extras-springsecurity5</artifactId> <1>
<version>2.1.2.RELEASE</version>
</dependency>
</dependencies>
@@ -32,7 +32,7 @@ Now that we have authenticated, let's update the application to display the user
[source,html]
----
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<title>Hello Spring Security</title>
<meta charset="utf-8" />
@@ -49,7 +49,7 @@ If you wish to override the Spring Security version, you may do so by providing
----
<properties>
<!-- ... -->
<spring-security.version>{spring-security-version}</spring.security.version>
<spring-security.version>{spring-security-version}</spring-security.version>
</dependencies>
----
@@ -14,7 +14,7 @@ spring:
github:
client-id: replace-with-client-id
client-secret: replace-with-client-secret
scopes: read:user,public_repo
scope: read:user,public_repo
----
You will need to replace the `client-id` and `client-secret` with values registered with GitHub.
@@ -1,16 +1,30 @@
[[webflux-oauth2-resource-server]]
= OAuth2 Resource Server
Spring Security provides OAuth2 Resource Server support with JWT tokens.
Spring Security supports protecting endpoints using https://tools.ietf.org/html/rfc7519[JWT]-encoded OAuth 2.0 https://tools.ietf.org/html/rfc6750.html[Bearer Tokens].
This is handy in circumstances where an application has federated its authority management out to an https://tools.ietf.org/html/rfc6749[authorization server] (for example, Okta or Ping Identity).
This authorization server can be consulted by Resource Servers to validate authority when serving requests.
[NOTE]
====
A complete working example can be found in {gh-samples-url}/boot/oauth2resourceserver-webflux[*OAuth 2.0 Resource Server WebFlux sample*].
====
The first step is to expose a `ReactiveJwtDecoder` as a `@Bean`.
In a Spring Boot application this can be done using:
== Dependencies
Most Resource Server support is collected into `spring-security-oauth2-resource-server`.
However, the support for decoding and verifying JWTs is in `spring-security-oauth2-jose`, meaning that both are necessary in order to have a working resource server that supports JWT-encoded Bearer Tokens.
[[webflux-oauth2-resource-server-minimal-configuration]]
== Minimal Configuration
When using https://spring.io/projects/spring-boot[Spring Boot], configuring an application as a resource server consists of two basic steps.
First, include the needed dependencies and second, indicate the location of the authorization server.
=== Specify the Authorization Server
In a Spring Boot application, to specify which authorization server to use, simply do:
[source,yml]
----
@@ -19,15 +33,108 @@ spring:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com/auth/realms/demo
issuer-uri: https://idp.example.com
----
The `issuer-uri` instructs Spring Security to leverage the endpoint at `https://idp.example.com/auth/realms/demo/.well-known/openid-configuration` to discover the configuration.
The above is all that is necessary to get a minimal Resource Server configured.
When new keys are made available, Spring Security will automatically rotate the keys used to validate the JWT tokens.
Where `https://idp.example.com` is the value contained in the `iss` claim for JWT tokens that the authorization server will issue.
Resource Server will use this property to further self-configure, discover the authorization server's public keys, and subsequently validate incoming JWTs.
By default each scope is mapped to an authority with the prefix `SCOPE_`.
For example, the following requires the scope of `message:read` for any URL that starts with `/messages/`.
[NOTE]
To use the `issuer-uri` property, it must also be true that `https://idp.example.com/.well-known/openid-configuration` is a supported endpoint for the authorization server.
This endpoint is referred to as a https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[Provider Configuration] endpoint.
And that's it!
=== Startup Expectations
When this property and these dependencies are used, Resource Server will automatically configure itself to validate JWT-encoded Bearer Tokens.
It achieves this through a deterministic startup process:
1. Hit the Provider Configuration endpoint, `https://the.issuer.location/.well-known/openid-configuration`, processing the response for the `jwks_url` property
2. Configure the validation strategy to query `jwks_url` for valid public keys
3. Configure the validation strategy to validate each JWTs `iss` claim against `https://idp.example.com`.
A consequence of this process is that the authorization server must be up and receiving requests in order for Resource Server to successfully start up.
[NOTE]
If the authorization server is down when Resource Server queries it (given appropriate timeouts), then startup will fail.
=== Runtime Expectations
Once the application is started up, Resource Server will attempt to process any request containing an `Authorization: Bearer` header:
[source,html]
----
GET / HTTP/1.1
Authorization: Bearer some-token-value # Resource Server will process this
----
So long as this scheme is indicated, Resource Server will attempt to process the request according to the Bearer Token specification.
Given a well-formed JWT token, Resource Server will:
1. Validate its signature against a public key obtained from the `jwks_url` endpoint during startup and matched against the JWTs header
2. Validate the JWTs `exp` and `nbf` timestamps and the JWTs `iss` claim, and
3. Map each scope to an authority with the prefix `SCOPE_`.
[NOTE]
As the authorization server makes available new keys, Spring Security will automatically rotate the keys used to validate the JWT tokens.
The resulting `Authentication#getPrincipal`, by default, is a Spring Security `Jwt` object, and `Authentication#getName` maps to the JWT's `sub` property, if one is present.
<<webflux-oauth2-resource-server-jwkseturi,How to Configure without Tying Resource Server startup to an authorization server's availability>>
<<webflux-oauth2-resource-server-sans-boot,How to Configure without Spring Boot>>
[[webflux-oauth2-resource-server-jwkseturi]]
=== Specifying the Authorization Server JWK Set Uri Directly
If the authorization server doesn't support the Provider Configuration endpoint, or if Resource Server must be able to start up independently from the authorization server, then `issuer-uri` can be exchanged for `jwk-set-uri`:
[source,yaml]
----
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: https://idp.example.com/.well-known/jwks.json
----
[NOTE]
The JWK Set uri is not standardized, but can typically be found in the authorization server's documentation
Consequently, Resource Server will not ping the authorization server at startup.
However, it will also no longer validate the `iss` claim in the JWT (since Resource Server no longer knows what the issuer value should be).
[NOTE]
This property can also be supplied directly on the <<webflux-oauth2-resource-server-jwkseturi-dsl,DSL>>.
[[webflux-oauth2-resource-server-sans-boot]]
=== Overriding or Replacing Boot Auto Configuration
There are two `@Bean` s that Spring Boot generates on Resource Server's behalf.
The first is a `SecurityWebFilterChain` that configures the app as a resource server:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
----
If the application doesn't expose a `SecurityWebFilterChain` bean, then Spring Boot will expose the above default one.
Replacing this is as simple as exposing the bean within the application:
[source,java]
----
@@ -44,4 +151,247 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
}
----
The above requires the scope of `message:read` for any URL that starts with `/messages/`.
Methods on the `oauth2ResourceServer` DSL will also override or replace auto configuration.
For example, the second `@Bean` Spring Boot creates is a `ReactiveJwtDecoder`, which decodes `String` tokens into validated instances of `Jwt`:
[source,java]
----
@Bean
public ReactiveJwtDecoder jwtDecoder() {
return ReactiveJwtDecoders.fromOidcIssuerLocation(issuerUri);
}
----
If the application doesn't expose a `ReactiveJwtDecoder` bean, then Spring Boot will expose the above default one.
And its configuration can be overridden using `jwkSetUri()` or replaced using `decoder()`.
[[webflux-oauth2-resource-server-jwkseturi-dsl]]
==== Using `jwkSetUri()`
An authorization server's JWK Set Uri can be configured <<webflux-oauth2-resource-server-jwkseturi,as a configuration property>> or it can be supplied in the DSL:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwkSetUri("https://idp.example.com/.well-known/jwks.json");
return http.build();
}
----
Using `jwkSetUri()` takes precedence over any configuration property.
[[webflux-oauth2-resource-server-decoder-dsl]]
==== Using `decoder()`
More powerful than `jwkSetUri()` is `decoder()`, which will completely replace any Boot auto configuration of `JwtDecoder`:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.decoder(myCustomDecoder());
return http.build();
}
----
This is handy when deeper configuration, like <<webflux-oauth2-resource-server-validation,validation>>, is necessary.
[[webflux-oauth2-resource-server-decoder-bean]]
==== Exposing a `ReactiveJwtDecoder` `@Bean`
Or, exposing a `ReactiveJwtDecoder` `@Bean` has the same effect as `decoder()`:
[source,java]
----
@Bean
public JwtDecoder jwtDecoder() {
return new NimbusReactiveJwtDecoder(jwkSetUri);
}
----
[[webflux-oauth2-resource-server-authorization]]
=== Configuring Authorization
A JWT that is issued from an OAuth 2.0 Authorization Server will typically either have a `scope` or `scp` attribute, indicating the scopes (or authorities) it's been granted, for example:
`{ ..., "scope" : "messages contacts"}`
When this is the case, Resource Server will attempt to coerce these scopes into a list of granted authorities, prefixing each scope with the string "SCOPE_".
This means that to protect an endpoint or method with a scope derived from a JWT, the corresponding expressions should include this prefix:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
.mvcMatchers("/messages/**").hasAuthority("SCOPE_messages")
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
----
Or similarly with method security:
[source,java]
----
@PreAuthorize("hasAuthority('SCOPE_messages')")
public List<Message> getMessages(...) {}
----
[[webflux-oauth2-resource-server-authorization-extraction]]
==== Extracting Authorities Manually
However, there are a number of circumstances where this default is insufficient.
For example, some authorization servers don't use the `scope` attribute, but instead have their own custom attribute.
Or, at other times, the resource server may need to adapt the attribute or a composition of attributes into internalized authorities.
To this end, the DSL exposes `jwtAuthenticationConverter()`:
[source,java]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(grantedAuthoritiesExtractor());
return http.build();
}
Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() {
GrantedAuthoritiesExtractor extractor = new GrantedAuthoritiesExtractor();
return new ReactiveJwtAuthenticationConverterAdapter(extractor);
}
----
which is responsible for converting a `Jwt` into an `Authentication`.
We can override this quite simply to alter the way granted authorities are derived:
[source,java]
----
static class GrantedAuthoritiesExtractor extends JwtAuthenticationConverter {
protected Collection<GrantedAuthorities> extractAuthorities(Jwt jwt) {
Collection<String> authorities = (Collection<String>)
jwt.getClaims().get("mycustomclaim");
return authorities.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
}
----
For more flexibility, the DSL supports entirely replacing the converter with any class that implements `Converter<Jwt, Mono<AbstractAuthenticationToken>>`:
[source,java]
----
static class CustomAuthenticationConverter implements Converter<Jwt, Mono<AbstractAuthenticationToken>> {
public AbstractAuthenticationToken convert(Jwt jwt) {
return Mono.just(jwt).map(this::doConversion);
}
}
----
[[webflux-oauth2-resource-server-validation]]
=== Configuring Validation
Using <<webflux-oauth2-resource-server-minimal-configuration,minimal Spring Boot configuration>>, indicating the authorization server's issuer uri, Resource Server will default to verifying the `iss` claim as well as the `exp` and `nbf` timestamp claims.
In circumstances where validation needs to be customized, Resource Server ships with two standard validators and also accepts custom `OAuth2TokenValidator` instances.
[[webflux-oauth2-resource-server-validation-clockskew]]
==== Customizing Timestamp Validation
JWT's typically have a window of validity, with the start of the window indicated in the `nbf` claim and the end indicated in the `exp` claim.
However, every server can experience clock drift, which can cause tokens to appear expired to one server, but not to another.
This can cause some implementation heartburn as the number of collaborating servers increases in a distributed system.
Resource Server uses `JwtTimestampValidator` to verify a token's validity window, and it can be configured with a `clockSkew` to alleviate the above problem:
[source,java]
----
@Bean
ReactiveJwtDecoder jwtDecoder() {
NimbusReactiveJwtDecoder jwtDecoder = (NimbusReactiveJwtDecoder)
ReactiveJwtDecoders.withOidcIssuerLocation(issuerUri);
OAuth2TokenValidator<Jwt> withClockSkew = new DelegatingOAuth2TokenValidator<>(
new JwtTimestampValidator(Duration.ofSeconds(60)),
new IssuerValidator(issuerUri));
jwtDecoder.setJwtValidator(withClockSkew);
return jwtDecoder;
}
----
[NOTE]
By default, Resource Server configures a clock skew of 30 seconds.
[[webflux-oauth2-resource-server-validation-custom]]
==== Configuring a Custom Validator
Adding a check for the `aud` claim is simple with the `OAuth2TokenValidator` API:
[source,java]
----
public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null);
public OAuth2TokenValidatorResult validate(Jwt jwt) {
if (jwt.getAudience().contains("messaging")) {
return OAuth2TokenValidatorResult.success();
} else {
return OAuth2TokenValidatorResult.failure(error);
}
}
}
----
Then, to add into a resource server, it's a matter of specifying the `ReactiveJwtDecoder` instance:
[source,java]
----
@Bean
ReactiveJwtDecoder jwtDecoder() {
NimbusReactiveJwtDecoder jwtDecoder = (NimbusReactiveJwtDecoder)
ReactiveJwtDecoders.withOidcIssuerLocation(issuerUri);
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator();
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
----
@@ -50,7 +50,7 @@ WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations,
[[webclient-implicit]]
== Implicit OAuth2AuthorizedClient
If we set `defaultOAuth2AuthorizedClient` to `true`in our setup and the user authenticated with oauth2Login (i.e. OIDC), then the current authentication is used to automatically provide the access token.
If we set `defaultOAuth2AuthorizedClient` to `true` in our setup and the user authenticated with oauth2Login (i.e. OIDC), then the current authentication is used to automatically provide the access token.
Alternatively, if we set `defaultClientRegistrationId` to a valid `ClientRegistration` id, that registration is used to provide the access token.
This is convenient, but in environments where not all endpoints should get the access token, it is dangerous (you might provide the wrong access token to an endpoint).
@@ -4,7 +4,7 @@
Spring Security has added Jackson Support for persisting Spring Security related classes.
This can improve the performance of serializing Spring Security related classes when working with distributed sessions (i.e. session replication, Spring Session, etc).
To use it, register the `JacksonJacksonModules.getModules(ClassLoader)` as http://wiki.fasterxml.com/JacksonFeatureModules[Jackson Modules].
To use it, register the `SecurityJackson2Modules.getModules(ClassLoader)` as http://wiki.fasterxml.com/JacksonFeatureModules[Jackson Modules].
[source,java]
----
@@ -183,7 +183,7 @@ A typical configuration might look like this:
----
Here we have four roles in a hierarchy `ROLE_ADMIN => ROLE_STAFF => ROLE_USER => ROLE_GUEST`.
A user who is authenticated with `ROLE_ADMIN`, will behave as if they have all four roles when security constraints are evaluated against an `AccessDecisionManager` cconfigured with the above `RoleHierarchyVoter`.
A user who is authenticated with `ROLE_ADMIN`, will behave as if they have all four roles when security constraints are evaluated against an `AccessDecisionManager` configured with the above `RoleHierarchyVoter`.
The `>` symbol can be thought of as meaning "includes".
Role hierarchies offer a convenient means of simplifying the access-control configuration data for your application and/or reducing the number of authorities which you need to assign to a user.
@@ -397,6 +397,11 @@ Spring Security supports protecting endpoints using https://tools.ietf.org/html/
This is handy in circumstances where an application has federated its authority management out to an https://tools.ietf.org/html/rfc6749[authorization server] (for example, Okta or Ping Identity).
This authorization server can be consulted by Resource Servers to validate authority when serving requests.
[NOTE]
====
A complete working example can be found in {gh-samples-url}/boot/oauth2resourceserver[*OAuth 2.0 Resource Server Servlet sample*].
====
=== Dependencies
Most Resource Server support is collected into `spring-security-oauth2-resource-server`.
@@ -417,25 +422,27 @@ security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://the.issuer.location
issuer-uri: https://idp.example.com
```
Where `https://the.issuer.location` is the value contained in the `iss` claim for JWT tokens that the authorization server will issue.
Resource Server will use this property to further self-configure and subsequently validate incoming JWTs.
Where `https://idp.example.com` is the value contained in the `iss` claim for JWT tokens that the authorization server will issue.
Resource Server will use this property to further self-configure, discover the authorization server's public keys, and subsequently validate incoming JWTs.
[NOTE]
To use the `issuer-uri` property, it must also be true that `https://the.issuer.location/.well-known/openid-configuration` is a supported endpoint for the authorization server.
To use the `issuer-uri` property, it must also be true that `https://idp.example.com/.well-known/openid-configuration` is a supported endpoint for the authorization server.
This endpoint is referred to as a https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[Provider Configuration] endpoint.
And that's it!
==== Startup Expectations
When this property and these dependencies are used, Resource Server will automatically configure itself to validate JWT-encoded Bearer Tokens.
It achieves this through a deterministic startup process:
1. Hit the Provider Configuration endpoint, `https://the.issuer.location/.well-known/openid-configuration`, processing the response for the `jwks_url` property
1. Hit the Provider Configuration endpoint, `https://idp.example.com/.well-known/openid-configuration`, processing the response for the `jwks_url` property
2. Configure the validation strategy to query `jwks_url` for valid public keys
3. Configure the validation strategy to validate each JWTs `iss` claim against `https://the.issuer.location`.
3. Configure the validation strategy to validate each JWTs `iss` claim against `https://idp.example.com`.
A consequence of this process is that the authorization server must be up and receiving requests in order for Resource Server to successfully start up.
@@ -444,7 +451,7 @@ If the authorization server is down when Resource Server queries it (given appro
==== Runtime Expectations
Once the application is started up, Resource Server will attempt to process any request containing an `Authorizatization: Bearer` header:
Once the application is started up, Resource Server will attempt to process any request containing an `Authorization: Bearer` header:
```http
GET / HTTP/1.1
@@ -453,10 +460,16 @@ Authorization: Bearer some-token-value # Resource Server will process this
So long as this scheme is indicated, Resource Server will attempt to process the request according to the Bearer Token specification.
Given a well-formed JWT token, Resource Server will validate the JWTs `exp` and `nbf` timestamps and the JWTs `iss` claim.
It will also validate the signature against a public key obtained from the `jwks_url` endpoint and matched against the JWTs header.
Given a well-formed JWT token, Resource Server will
The resulting `Authentication#getPrincipal`, by default, is a Spring Security `Jwt` object, and `Authentication#getName` map's to the JWT's `sub` property, if one is present.
1. Validate its signature against a public key obtained from the `jwks_url` endpoint during startup and matched against the JWTs header
2. Validate the JWTs `exp` and `nbf` timestamps and the JWTs `iss` claim, and
3. Map each scope to an authority with the prefix `SCOPE_`.
[NOTE]
As the authorization server makes available new keys, Spring Security will automatically rotate the keys used to validate the JWT tokens.
The resulting `Authentication#getPrincipal`, by default, is a Spring Security `Jwt` object, and `Authentication#getName` maps to the JWT's `sub` property, if one is present.
From here, consider jumping to:
@@ -474,7 +487,7 @@ security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: https://the.issuer.location/.well-known/jwks.json
jwk-set-uri: https://idp.example.com/.well-known/jwks.json
```
[NOTE]
@@ -514,7 +527,7 @@ public class MyCustomSecurityConfiguration extends WebSecurityConfigurerAdapter
protected void configure(HttpSecurity http) {
http
.authorizeRequests()
.mvcMatchers("/admin/**").hasAuthority("SCOPE_admin")
.mvcMatchers("/messages/**").hasAuthority("SCOPE_message:read")
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
@@ -524,6 +537,8 @@ public class MyCustomSecurityConfiguration extends WebSecurityConfigurerAdapter
}
```
The above requires the scope of `message:read` for any URL that starts with `/messages/`.
Methods on the `oauth2ResourceServer` DSL will also override or replace auto configuration.
For example, the second `@Bean` Spring Boot creates is a `JwtDecoder`, which decodes `String` tokens into validated instances of `Jwt`:
@@ -554,7 +569,7 @@ public class DirectlyConfiguredJwkSetUri extends WebSecurityConfigurerAdapter {
.and()
.oauth2ResourceServer()
.jwt()
.jwkSetUri("https://the.issuer.location/.well-known/jwks.json");
.jwkSetUri("https://idp.example.com/.well-known/jwks.json");
}
}
```
@@ -581,7 +596,7 @@ public class DirectlyConfiguredJwkSetUri extends WebSecurityConfigurerAdapter {
}
```
This is handy when deeper configuration, like <<oauth2resourceserver-validator,validation>>, <<oauth2resourceserver-claimsetmapping,mapping>>, or <<oauth2resourceserver-timeouts,request timeouts>>, is necessary.
This is handy when deeper configuration, like <<oauth2resourceserver-validation,validation>>, <<oauth2resourceserver-claimsetmapping,mapping>>, or <<oauth2resourceserver-timeouts,request timeouts>>, is necessary.
[[oauth2resourceserver-decoder-bean]]
==== Exposing a `JwtDecoder` `@Bean`
@@ -602,7 +617,7 @@ A JWT that is issued from an OAuth 2.0 Authorization Server will typically eithe
`{ ..., "scope" : "messages contacts"}`
When this is the case, Resource Server will attempt to coerce these scopes into a list of granted authorities, prefixing each scope with the prefix "SCOPE_".
When this is the case, Resource Server will attempt to coerce these scopes into a list of granted authorities, prefixing each scope with the string "SCOPE_".
This means that to protect an endpoint or method with a scope derived from a JWT, the corresponding expressions should include this prefix:
@@ -633,7 +648,7 @@ public List<Message> getMessages(...) {}
==== Extracting Authorities Manually
However, there are a number of circumstances where this default is insufficient.
For example, some authorization server's don't use the `scope` attribute, but instead have their own custom attribute.
For example, some authorization servers don't use the `scope` attribute, but instead have their own custom attribute.
Or, at other times, the resource server may need to adapt the attribute or a composition of attributes into internalized authorities.
To this end, the DSL exposes `jwtAuthenticationConverter()`:
@@ -776,13 +791,12 @@ For these purposes, Resource Server supports mapping the JWT claim set with `Map
By default, `MappedJwtClaimSetConverter` will attempt to coerce claims into the following types:
|============
| Claim | Java Type
| `aud` | `Collection<String>`
| `exp` | `Instant`
| `iat` | `Instant`
| `iss` | `URL`
| `iss` | `String`
| `jti` | `String`
| `nbf` | `Instant`
| `sub` | `String`
+3 -3
View File
@@ -1,3 +1,3 @@
gaeVersion=1.9.66
springBootVersion=2.1.0.M4
version=5.1.2.BUILD-SNAPSHOT
gaeVersion=1.9.71
springBootVersion=2.1.2.RELEASE
version=5.1.4.RELEASE
+28 -29
View File
@@ -1,13 +1,13 @@
if (!project.hasProperty('reactorVersion')) {
ext.reactorVersion = 'Californium-SR1'
ext.reactorVersion = 'Californium-SR5'
}
if (!project.hasProperty('springVersion')) {
ext.springVersion = '5.1.1.RELEASE'
ext.springVersion = '5.1.5.RELEASE'
}
if (!project.hasProperty('springDataVersion')) {
ext.springDataVersion = 'Lovelace-SR1'
ext.springDataVersion = 'Lovelace-SR5'
}
dependencyManagement {
@@ -17,17 +17,17 @@ dependencyManagement {
mavenBom "org.springframework.data:spring-data-releasetrain:${springDataVersion}"
}
dependencies {
dependency 'cglib:cglib-nodep:3.2.8'
dependency 'com.squareup.okhttp3:mockwebserver:3.10.0'
dependency 'cglib:cglib-nodep:3.2.10'
dependency 'com.squareup.okhttp3:mockwebserver:3.12.1'
dependency 'opensymphony:sitemesh:2.4.2'
dependency 'org.gebish:geb-spock:0.10.0'
dependency 'org.jasig.cas:cas-server-webapp:4.2.7'
dependency 'org.powermock:powermock-api-mockito2:2.0.0-beta.5'
dependency 'org.powermock:powermock-api-support:2.0.0-beta.5'
dependency 'org.powermock:powermock-core:2.0.0-beta.5'
dependency 'org.powermock:powermock-module-junit4-common:2.0.0-beta.5'
dependency 'org.powermock:powermock-module-junit4:2.0.0-beta.5'
dependency 'org.powermock:powermock-reflect:2.0.0-beta.5'
dependency 'org.powermock:powermock-api-mockito2:2.0.0'
dependency 'org.powermock:powermock-api-support:2.0.0'
dependency 'org.powermock:powermock-core:2.0.0'
dependency 'org.powermock:powermock-module-junit4-common:2.0.0'
dependency 'org.powermock:powermock-module-junit4:2.0.0'
dependency 'org.powermock:powermock-reflect:2.0.0'
dependency 'org.python:jython:2.5.0'
dependency 'org.spockframework:spock-core:1.0-groovy-2.4'
dependency 'org.spockframework:spock-spring:1.0-groovy-2.4'
@@ -40,9 +40,9 @@ 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.7'
dependency 'com.fasterxml.jackson.core:jackson-core:2.9.7'
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.7'
dependency 'com.fasterxml.jackson.core:jackson-annotations:2.9.8'
dependency 'com.fasterxml.jackson.core:jackson-core:2.9.8'
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
dependency 'com.fasterxml:classmate:1.3.4'
dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1'
dependency 'com.google.appengine:appengine-api-1.0-sdk:1.9.64'
@@ -56,11 +56,11 @@ dependencyManagement {
dependency 'com.nimbusds:lang-tag:1.4.3'
dependency 'com.nimbusds:nimbus-jose-jwt:6.0.2'
dependency 'com.nimbusds:oauth2-oidc-sdk:6.0'
dependency 'com.squareup.okhttp3:okhttp:3.9.0'
dependency 'com.squareup.okhttp3:okhttp:3.12.1'
dependency 'com.squareup.okio:okio:1.13.0'
dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1'
dependency 'com.sun.xml.bind:jaxb-impl:2.3.0.1'
dependency 'com.unboundid:unboundid-ldapsdk:4.0.8'
dependency 'com.unboundid:unboundid-ldapsdk:4.0.9'
dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1'
dependency 'commons-cli:commons-cli:1.4'
dependency 'commons-codec:commons-codec:1.11'
@@ -81,12 +81,12 @@ dependencyManagement {
dependency 'javax.xml.bind:jaxb-api:2.4.0-b180830.0359'
dependency 'junit:junit:4.12'
dependency 'ldapsdk:ldapsdk:4.1'
dependency 'net.bytebuddy:byte-buddy-agent:1.7.9'
dependency 'net.bytebuddy:byte-buddy:1.7.9'
dependency 'net.bytebuddy:byte-buddy-agent:1.8.3'
dependency 'net.bytebuddy:byte-buddy:1.8.3'
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.5'
dependency 'net.sf.ehcache:ehcache:2.10.6'
dependency 'net.sourceforge.htmlunit:htmlunit:2.33'
dependency 'net.sourceforge.htmlunit:neko-htmlunit:2.32'
dependency 'net.sourceforge.nekohtml:nekohtml:1.9.22'
@@ -127,7 +127,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.6'
dependency 'org.apache.httpcomponents:httpclient:4.5.7'
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'
@@ -140,12 +140,12 @@ dependencyManagement {
dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:8.0.44'
dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.23'
dependency 'org.apache.tomcat:tomcat-annotations-api:8.5.23'
dependency 'org.aspectj:aspectjrt:1.9.1'
dependency 'org.aspectj:aspectjtools:1.9.1'
dependency 'org.aspectj:aspectjweaver:1.9.1'
dependency 'org.aspectj:aspectjrt:1.9.2'
dependency 'org.aspectj:aspectjtools:1.9.2'
dependency 'org.aspectj:aspectjweaver:1.9.2'
dependency 'org.assertj:assertj-core:3.11.1'
dependency 'org.attoparser:attoparser:2.0.4.RELEASE'
dependency 'org.bouncycastle:bcpkix-jdk15on:1.60'
dependency 'org.bouncycastle:bcpkix-jdk15on:1.61'
dependency 'org.bouncycastle:bcprov-jdk15on:1.58'
dependency 'org.codehaus.groovy:groovy-all:2.4.14'
dependency 'org.codehaus.groovy:groovy-json:2.4.14'
@@ -174,15 +174,15 @@ dependencyManagement {
dependency 'org.hibernate:hibernate-entitymanager:5.3.6.Final'
dependency 'org.hibernate:hibernate-validator:6.0.13.Final'
dependency 'org.hsqldb:hsqldb:2.4.1'
dependency 'org.jasig.cas.client:cas-client-core:3.5.0'
dependency 'org.jasig.cas.client:cas-client-core:3.5.1'
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.22.0'
dependency 'org.mockito:mockito-core:2.23.4'
dependency 'org.objenesis:objenesis:2.6'
dependency 'org.openid4java:openid4java-nodeps:0.9.6'
dependency 'org.ow2.asm:asm:6.0'
dependency 'org.ow2.asm:asm:6.2.1'
dependency 'org.reactivestreams:reactive-streams:1.0.1'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.33.0'
dependency 'org.seleniumhq.selenium:selenium-java:3.14.0'
@@ -195,8 +195,7 @@ dependencyManagement {
dependency 'org.slf4j:slf4j-nop:1.7.25'
dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111'
dependency 'org.springframework.ldap:spring-ldap-core:2.3.2.RELEASE'
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.10.RELEASE'
dependency 'org.thymeleaf:thymeleaf:3.0.9.RELEASE'
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE'
dependency 'org.unbescape:unbescape:1.1.5.RELEASE'
dependency 'org.w3c.css:sac:1.3'
dependency 'xalan:serializer:2.7.2'
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -25,6 +25,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Mono;
import java.util.Set;
@@ -64,7 +65,17 @@ public class WebClientReactiveClientCredentialsTokenResponseClient implements Re
.headers(headers(clientRegistration))
.body(body)
.exchange()
.flatMap(response -> response.body(oauth2AccessTokenResponse()))
.flatMap(response ->{
if (!response.statusCode().is2xxSuccessful()){
// extract the contents of this into a method named oauth2AccessTokenResponse but has an argument for the response
throw WebClientResponseException.create(response.rawStatusCode(),
"Cannot get token, expected 2xx HTTP Status code",
null,
null,
null
);
}
return response.body(oauth2AccessTokenResponse()); })
.map(response -> {
if (response.getAccessToken().getScopes().isEmpty()) {
response = OAuth2AccessTokenResponse.withResponse(response)
@@ -18,16 +18,13 @@ package org.springframework.security.oauth2.client.registration;
import org.springframework.util.Assert;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.stream.Collector;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toConcurrentMap;
import java.util.stream.Collectors;
/**
* A {@link ClientRegistrationRepository} that stores {@link ClientRegistration}(s) in-memory.
@@ -39,6 +36,7 @@ import static java.util.stream.Collectors.toConcurrentMap;
* @see ClientRegistration
*/
public final class InMemoryClientRegistrationRepository implements ClientRegistrationRepository, Iterable<ClientRegistration> {
private final Map<String, ClientRegistration> registrations;
/**
@@ -47,7 +45,8 @@ public final class InMemoryClientRegistrationRepository implements ClientRegistr
* @param registrations the client registration(s)
*/
public InMemoryClientRegistrationRepository(ClientRegistration... registrations) {
this(Arrays.asList(registrations));
Assert.notEmpty(registrations, "registrations cannot be empty");
this.registrations = createClientRegistrationIdToClientRegistration(Arrays.asList(registrations));
}
/**
@@ -57,10 +56,13 @@ public final class InMemoryClientRegistrationRepository implements ClientRegistr
*/
public InMemoryClientRegistrationRepository(List<ClientRegistration> registrations) {
Assert.notEmpty(registrations, "registrations cannot be empty");
Collector<ClientRegistration, ?, ConcurrentMap<String, ClientRegistration>> collector =
toConcurrentMap(ClientRegistration::getRegistrationId, Function.identity());
this.registrations = registrations.stream()
.collect(collectingAndThen(collector, Collections::unmodifiableMap));
this.registrations = createClientRegistrationIdToClientRegistration(registrations);
}
private static Map<String, ClientRegistration> createClientRegistrationIdToClientRegistration(Collection<ClientRegistration> registrations) {
return Collections.unmodifiableMap(registrations.stream()
.peek(registration -> Assert.notNull(registration, "registrations cannot contain null values"))
.collect(Collectors.toMap(ClientRegistration::getRegistrationId, Function.identity())));
}
@Override
@@ -17,12 +17,6 @@ package org.springframework.security.oauth2.client.registration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import reactor.core.publisher.Mono;
@@ -37,7 +31,7 @@ import reactor.core.publisher.Mono;
public final class InMemoryReactiveClientRegistrationRepository
implements ReactiveClientRegistrationRepository, Iterable<ClientRegistration> {
private final Map<String, ClientRegistration> clientIdToClientRegistration;
private final InMemoryClientRegistrationRepository delegate;
/**
* Constructs an {@code InMemoryReactiveClientRegistrationRepository} using the provided parameters.
@@ -45,12 +39,7 @@ public final class InMemoryReactiveClientRegistrationRepository
* @param registrations the client registration(s)
*/
public InMemoryReactiveClientRegistrationRepository(ClientRegistration... registrations) {
Assert.notEmpty(registrations, "registrations cannot be empty");
this.clientIdToClientRegistration = new ConcurrentReferenceHashMap<>();
for (ClientRegistration registration : registrations) {
Assert.notNull(registration, "registrations cannot contain null values");
this.clientIdToClientRegistration.put(registration.getRegistrationId(), registration);
}
this.delegate = new InMemoryClientRegistrationRepository(registrations);
}
/**
@@ -59,15 +48,12 @@ public final class InMemoryReactiveClientRegistrationRepository
* @param registrations the client registration(s)
*/
public InMemoryReactiveClientRegistrationRepository(List<ClientRegistration> registrations) {
Assert.notEmpty(registrations, "registrations cannot be null or empty");
this.clientIdToClientRegistration = registrations.stream()
.collect(Collectors.toConcurrentMap(ClientRegistration::getRegistrationId, Function.identity()));
this.delegate = new InMemoryClientRegistrationRepository(registrations);
}
@Override
public Mono<ClientRegistration> findByRegistrationId(String registrationId) {
return Mono.justOrEmpty(this.clientIdToClientRegistration.get(registrationId));
return Mono.justOrEmpty(this.delegate.findByRegistrationId(registrationId));
}
/**
@@ -77,6 +63,6 @@ public final class InMemoryReactiveClientRegistrationRepository
*/
@Override
public Iterator<ClientRegistration> iterator() {
return this.clientIdToClientRegistration.values().iterator();
return delegate.iterator();
}
}
@@ -60,7 +60,7 @@ public class OAuth2UserRequestEntityConverter implements Converter<OAuth2UserReq
httpMethod = HttpMethod.POST;
}
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())
.build()
.toUri();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@ import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse;
@@ -267,7 +268,11 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
.build();
return next.exchange(refreshRequest)
.flatMap(refreshResponse -> refreshResponse.body(oauth2AccessTokenResponse()))
.map(accessTokenResponse -> new OAuth2AuthorizedClient(authorizedClient.getClientRegistration(), authorizedClient.getPrincipalName(), accessTokenResponse.getAccessToken(), accessTokenResponse.getRefreshToken()))
.map(accessTokenResponse -> {
OAuth2RefreshToken refreshToken = Optional.ofNullable(accessTokenResponse.getRefreshToken())
.orElse(authorizedClient.getRefreshToken());
return new OAuth2AuthorizedClient(authorizedClient.getClientRegistration(), authorizedClient.getPrincipalName(), accessTokenResponse.getAccessToken(), refreshToken);
})
.flatMap(result -> this.authorizedClientRepository.saveAuthorizedClient(result, authentication, exchange)
.thenReturn(result));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,9 @@
package org.springframework.security.oauth2.client.web.reactive.function.client;
import org.reactivestreams.Subscription;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
@@ -44,8 +47,12 @@ import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.CoreSubscriber;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Operators;
import reactor.core.scheduler.Schedulers;
import reactor.util.context.Context;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -98,7 +105,9 @@ import static org.springframework.security.oauth2.core.web.reactive.function.OAu
* @author Rob Winch
* @since 5.1
*/
public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implements ExchangeFilterFunction {
public final class ServletOAuth2AuthorizedClientExchangeFilterFunction
implements ExchangeFilterFunction, InitializingBean, DisposableBean {
/**
* The request attribute name used to locate the {@link OAuth2AuthorizedClient}.
*/
@@ -108,6 +117,8 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
private static final String HTTP_SERVLET_REQUEST_ATTR_NAME = HttpServletRequest.class.getName();
private static final String HTTP_SERVLET_RESPONSE_ATTR_NAME = HttpServletResponse.class.getName();
private static final String REQUEST_CONTEXT_OPERATOR_KEY = RequestContextSubscriber.class.getName();
private Clock clock = Clock.systemUTC();
private Duration accessTokenExpiresSkew = Duration.ofMinutes(1);
@@ -123,7 +134,8 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
private String defaultClientRegistrationId;
public ServletOAuth2AuthorizedClientExchangeFilterFunction() {}
public ServletOAuth2AuthorizedClientExchangeFilterFunction() {
}
public ServletOAuth2AuthorizedClientExchangeFilterFunction(
ClientRegistrationRepository clientRegistrationRepository,
@@ -132,6 +144,16 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
this.authorizedClientRepository = authorizedClientRepository;
}
@Override
public void afterPropertiesSet() throws Exception {
Hooks.onLastOperator(REQUEST_CONTEXT_OPERATOR_KEY, Operators.lift((s, sub) -> createRequestContextSubscriber(sub)));
}
@Override
public void destroy() throws Exception {
Hooks.resetOnLastOperator(REQUEST_CONTEXT_OPERATOR_KEY);
}
/**
* Sets the {@link OAuth2AccessTokenResponseClient} to be used for getting an {@link OAuth2AuthorizedClient} for
* client_credentials grant.
@@ -266,15 +288,36 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
Optional<OAuth2AuthorizedClient> attribute = request.attribute(OAUTH2_AUTHORIZED_CLIENT_ATTR_NAME)
.map(OAuth2AuthorizedClient.class::cast);
return Mono.justOrEmpty(attribute)
.flatMap(authorizedClient -> authorizedClient(request, next, authorizedClient))
return Mono.just(request)
.filter(req -> req.attribute(OAUTH2_AUTHORIZED_CLIENT_ATTR_NAME).isPresent())
.switchIfEmpty(mergeRequestAttributesFromContext(request))
.filter(req -> req.attribute(OAUTH2_AUTHORIZED_CLIENT_ATTR_NAME).isPresent())
.flatMap(req -> authorizedClient(req, next, getOAuth2AuthorizedClient(req.attributes())))
.map(authorizedClient -> bearer(request, authorizedClient))
.flatMap(next::exchange)
.switchIfEmpty(next.exchange(request));
}
private Mono<ClientRequest> mergeRequestAttributesFromContext(ClientRequest request) {
return Mono.just(ClientRequest.from(request))
.flatMap(builder -> Mono.subscriberContext()
.map(ctx -> builder.attributes(attrs -> populateRequestAttributes(attrs, ctx))))
.map(ClientRequest.Builder::build);
}
private void populateRequestAttributes(Map<String, Object> attrs, Context ctx) {
if (ctx.hasKey(HTTP_SERVLET_REQUEST_ATTR_NAME)) {
attrs.putIfAbsent(HTTP_SERVLET_REQUEST_ATTR_NAME, ctx.get(HTTP_SERVLET_REQUEST_ATTR_NAME));
}
if (ctx.hasKey(HTTP_SERVLET_RESPONSE_ATTR_NAME)) {
attrs.putIfAbsent(HTTP_SERVLET_RESPONSE_ATTR_NAME, ctx.get(HTTP_SERVLET_RESPONSE_ATTR_NAME));
}
if (ctx.hasKey(AUTHENTICATION_ATTR_NAME)) {
attrs.putIfAbsent(AUTHENTICATION_ATTR_NAME, ctx.get(AUTHENTICATION_ATTR_NAME));
}
populateDefaultOAuth2AuthorizedClient(attrs);
}
private void populateDefaultRequestResponse(Map<String, Object> attrs) {
if (attrs.containsKey(HTTP_SERVLET_REQUEST_ATTR_NAME) && attrs.containsKey(
HTTP_SERVLET_RESPONSE_ATTR_NAME)) {
@@ -385,7 +428,11 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
.build();
return next.exchange(refreshRequest)
.flatMap(response -> response.body(oauth2AccessTokenResponse()))
.map(accessTokenResponse -> new OAuth2AuthorizedClient(authorizedClient.getClientRegistration(), authorizedClient.getPrincipalName(), accessTokenResponse.getAccessToken(), accessTokenResponse.getRefreshToken()))
.map(accessTokenResponse -> {
OAuth2RefreshToken refreshToken = Optional.ofNullable(accessTokenResponse.getRefreshToken())
.orElse(authorizedClient.getRefreshToken());
return new OAuth2AuthorizedClient(authorizedClient.getClientRegistration(), authorizedClient.getPrincipalName(), accessTokenResponse.getAccessToken(), refreshToken);
})
.map(result -> {
Authentication principal = (Authentication) request.attribute(
AUTHENTICATION_ATTR_NAME).orElse(new PrincipalNameAuthentication(authorizedClient.getPrincipalName()));
@@ -421,6 +468,19 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
.build();
}
private <T> CoreSubscriber<T> createRequestContextSubscriber(CoreSubscriber<T> delegate) {
HttpServletRequest request = null;
HttpServletResponse response = null;
ServletRequestAttributes requestAttributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
request = requestAttributes.getRequest();
response = requestAttributes.getResponse();
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new RequestContextSubscriber<>(delegate, request, response, authentication);
}
private static BodyInserters.FormInserter<String> refreshTokenBody(String refreshToken) {
return BodyInserters
.fromFormData("grant_type", AuthorizationGrantType.REFRESH_TOKEN.getValue())
@@ -494,4 +554,55 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
return new UnsupportedOperationException("Not Supported");
}
}
private static class RequestContextSubscriber<T> implements CoreSubscriber<T> {
private static final String CONTEXT_DEFAULTED_ATTR_NAME = RequestContextSubscriber.class.getName().concat(".CONTEXT_DEFAULTED_ATTR_NAME");
private final CoreSubscriber<T> delegate;
private final HttpServletRequest request;
private final HttpServletResponse response;
private final Authentication authentication;
private RequestContextSubscriber(CoreSubscriber<T> delegate,
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
this.delegate = delegate;
this.request = request;
this.response = response;
this.authentication = authentication;
}
@Override
public Context currentContext() {
Context context = this.delegate.currentContext();
if (context.hasKey(CONTEXT_DEFAULTED_ATTR_NAME)) {
return context;
}
return Context.of(
CONTEXT_DEFAULTED_ATTR_NAME, Boolean.TRUE,
HTTP_SERVLET_REQUEST_ATTR_NAME, this.request,
HTTP_SERVLET_RESPONSE_ATTR_NAME, this.response,
AUTHENTICATION_ATTR_NAME, this.authentication);
}
@Override
public void onSubscribe(Subscription s) {
this.delegate.onSubscribe(s);
}
@Override
public void onNext(T t) {
this.delegate.onNext(t);
}
@Override
public void onError(Throwable t) {
this.delegate.onError(t);
}
@Override
public void onComplete() {
this.delegate.onComplete();
}
}
}
@@ -28,6 +28,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import static org.assertj.core.api.Assertions.*;
@@ -116,6 +117,23 @@ public class WebClientReactiveClientCredentialsTokenResponseClientTests {
assertThat(response.getAccessToken().getScopes()).isEqualTo(registration.getScopes());
}
@Test(expected = WebClientResponseException.class)
// gh-6089
public void getTokenResponseWhenInvalidResponse() throws WebClientResponseException {
ClientRegistration registration = this.clientRegistration.build();
enqueueUnexpectedResponse();
OAuth2ClientCredentialsGrantRequest request = new OAuth2ClientCredentialsGrantRequest(registration);
OAuth2AccessTokenResponse response = this.client.getTokenResponse(request).block();
}
private void enqueueUnexpectedResponse(){
MockResponse response = new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.setResponseCode(301);
this.server.enqueue(response);
}
private void enqueueJson(String body) {
MockResponse response = new MockResponse()
@@ -316,7 +316,7 @@ public class OidcUserServiceTests {
this.userService.loadUser(new OidcUserRequest(this.clientRegistration, this.accessToken, this.idToken));
assertThat(this.server.takeRequest(1, TimeUnit.SECONDS).getHeader(HttpHeaders.ACCEPT))
.isEqualTo(MediaType.APPLICATION_JSON_UTF8_VALUE);
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
}
// gh-5500
@@ -341,7 +341,7 @@ public class OidcUserServiceTests {
this.userService.loadUser(new OidcUserRequest(this.clientRegistration, this.accessToken, this.idToken));
RecordedRequest request = this.server.takeRequest();
assertThat(request.getMethod()).isEqualTo(HttpMethod.GET.name());
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_UTF8_VALUE);
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
assertThat(request.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer " + this.accessToken.getTokenValue());
}
@@ -367,7 +367,7 @@ public class OidcUserServiceTests {
this.userService.loadUser(new OidcUserRequest(this.clientRegistration, this.accessToken, this.idToken));
RecordedRequest request = this.server.takeRequest();
assertThat(request.getMethod()).isEqualTo(HttpMethod.POST.name());
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_UTF8_VALUE);
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
assertThat(request.getHeader(HttpHeaders.CONTENT_TYPE)).contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
assertThat(request.getBody().readUtf8()).isEqualTo("access_token=" + this.accessToken.getTokenValue());
}
@@ -35,6 +35,12 @@ public class InMemoryClientRegistrationRepositoryTests {
private InMemoryClientRegistrationRepository clients = new InMemoryClientRegistrationRepository(this.registration);
@Test(expected = IllegalArgumentException.class)
public void constructorVarArgsListClientRegistrationWhenNullThenIllegalArgumentException() {
ClientRegistration nullRegistration = null;
new InMemoryClientRegistrationRepository(nullRegistration);
}
@Test(expected = IllegalArgumentException.class)
public void constructorListClientRegistrationWhenNullThenIllegalArgumentException() {
List<ClientRegistration> registrations = null;
@@ -19,6 +19,7 @@ package org.springframework.security.oauth2.client.registration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
@@ -61,10 +62,16 @@ public class InMemoryReactiveClientRegistrationRepositoryTests {
.isInstanceOf(IllegalArgumentException.class);
}
@Test
public void constructorWhenClientRegistrationListHasNullThenIllegalArgumentException() {
List<ClientRegistration> registrations = Arrays.asList(null, registration);
assertThatThrownBy(() -> new InMemoryReactiveClientRegistrationRepository(registrations))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
public void constructorWhenClientRegistrationIsNullThenIllegalArgumentException() {
ClientRegistration registration = null;
assertThatThrownBy(() -> new InMemoryReactiveClientRegistrationRepository(registration))
assertThatThrownBy(() -> new InMemoryReactiveClientRegistrationRepository(registration, null))
.isInstanceOf(IllegalArgumentException.class);
}
@@ -277,7 +277,7 @@ public class DefaultOAuth2UserServiceTests {
this.userService.loadUser(new OAuth2UserRequest(this.clientRegistration, this.accessToken));
assertThat(this.server.takeRequest(1, TimeUnit.SECONDS).getHeader(HttpHeaders.ACCEPT))
.isEqualTo(MediaType.APPLICATION_JSON_UTF8_VALUE);
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
}
// gh-5500
@@ -303,7 +303,7 @@ public class DefaultOAuth2UserServiceTests {
this.userService.loadUser(new OAuth2UserRequest(this.clientRegistration, this.accessToken));
RecordedRequest request = this.server.takeRequest();
assertThat(request.getMethod()).isEqualTo(HttpMethod.GET.name());
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_UTF8_VALUE);
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
assertThat(request.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer " + this.accessToken.getTokenValue());
}
@@ -330,7 +330,7 @@ public class DefaultOAuth2UserServiceTests {
this.userService.loadUser(new OAuth2UserRequest(this.clientRegistration, this.accessToken));
RecordedRequest request = this.server.takeRequest();
assertThat(request.getMethod()).isEqualTo(HttpMethod.POST.name());
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_UTF8_VALUE);
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
assertThat(request.getHeader(HttpHeaders.CONTENT_TYPE)).contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
assertThat(request.getBody().readUtf8()).isEqualTo("access_token=" + this.accessToken.getTokenValue());
}
@@ -56,7 +56,7 @@ public class OAuth2UserRequestEntityConverterTests {
clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri());
HttpHeaders headers = requestEntity.getHeaders();
assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8);
assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON);
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo(
"Bearer " + userRequest.getAccessToken().getTokenValue());
}
@@ -77,7 +77,7 @@ public class OAuth2UserRequestEntityConverterTests {
clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri());
HttpHeaders headers = requestEntity.getHeaders();
assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8);
assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON);
assertThat(headers.getContentType()).isEqualTo(
MediaType.valueOf(APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
@@ -107,7 +107,11 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
assertThat(authorizationRequest.getState()).isNotNull();
assertThat(authorizationRequest.getAdditionalParameters())
.containsExactly(entry(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()));
assertThat(authorizationRequest.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?response_type=code&client_id=client-id&scope=read%3Auser&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fregistration-id");
assertThat(authorizationRequest.getAuthorizationRequestUri())
.matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id&" +
"scope=read:user&state=.{15,}&" +
"redirect_uri=http://localhost/login/oauth2/code/registration-id");
}
@Test
@@ -164,7 +168,11 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
request.setServletPath(requestUri);
OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
assertThat(authorizationRequest.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?response_type=code&client_id=client-id&scope=read%3Auser&state=.{15,}&redirect_uri=http%3A%2F%2Fexample.com%2Flogin%2Foauth2%2Fcode%2Fregistration-id");
assertThat(authorizationRequest.getAuthorizationRequestUri())
.matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id&" +
"scope=read:user&state=.{15,}&" +
"redirect_uri=http://example.com/login/oauth2/code/registration-id");
}
@Test
@@ -178,7 +186,11 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
request.setServletPath(requestUri);
OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
assertThat(authorizationRequest.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?response_type=code&client_id=client-id&scope=read%3Auser&state=.{15,}&redirect_uri=https%3A%2F%2Fexample.com%2Flogin%2Foauth2%2Fcode%2Fregistration-id");
assertThat(authorizationRequest.getAuthorizationRequestUri())
.matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id&" +
"scope=read:user&state=.{15,}&" +
"redirect_uri=https://example.com/login/oauth2/code/registration-id");
}
@Test
@@ -189,7 +201,11 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
request.setServletPath(requestUri);
OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request, clientRegistration.getRegistrationId());
assertThat(authorizationRequest.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?response_type=code&client_id=client-id&scope=read%3Auser&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Fauthorize%2Foauth2%2Fcode%2Fregistration-id");
assertThat(authorizationRequest.getAuthorizationRequestUri())
.matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id&" +
"scope=read:user&state=.{15,}&" +
"redirect_uri=http://localhost/authorize/oauth2/code/registration-id");
}
@Test
@@ -200,7 +216,11 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
request.setServletPath(requestUri);
OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
assertThat(authorizationRequest.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?response_type=code&client_id=client-id-2&scope=read%3Auser&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fregistration-id-2");
assertThat(authorizationRequest.getAuthorizationRequestUri())
.matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id-2&" +
"scope=read:user&state=.{15,}&" +
"redirect_uri=http://localhost/login/oauth2/code/registration-id-2");
}
@Test
@@ -212,7 +232,11 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
request.setServletPath(requestUri);
OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
assertThat(authorizationRequest.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?response_type=code&client_id=client-id&scope=read%3Auser&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Fauthorize%2Foauth2%2Fcode%2Fregistration-id");
assertThat(authorizationRequest.getAuthorizationRequestUri())
.matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id&" +
"scope=read:user&state=.{15,}&" +
"redirect_uri=http://localhost/authorize/oauth2/code/registration-id");
}
@Test
@@ -224,6 +248,10 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
request.setServletPath(requestUri);
OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
assertThat(authorizationRequest.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?response_type=code&client_id=client-id-2&scope=read%3Auser&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fregistration-id-2");
assertThat(authorizationRequest.getAuthorizationRequestUri())
.matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id-2&" +
"scope=read:user&state=.{15,}&" +
"redirect_uri=http://localhost/login/oauth2/code/registration-id-2");
}
}
@@ -151,7 +151,10 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
verifyZeroInteractions(filterChain);
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?response_type=code&client_id=client-id&scope=read%3Auser&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fregistration-id");
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id&" +
"scope=read:user&state=.{15,}&" +
"redirect_uri=http://localhost/login/oauth2/code/registration-id");
}
@Test
@@ -187,7 +190,10 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
verifyZeroInteractions(filterChain);
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?response_type=token&client_id=client-id&scope=read%3Auser&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Fauthorize%2Foauth2%2Fimplicit%2Fregistration-3");
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
"response_type=token&client_id=client-id&" +
"scope=read:user&state=.{15,}&" +
"redirect_uri=http://localhost/authorize/oauth2/implicit/registration-3");
}
@Test
@@ -225,7 +231,10 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
verifyZeroInteractions(filterChain);
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?response_type=code&client_id=client-id&scope=read%3Auser&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fregistration-id");
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id&" +
"scope=read:user&state=.{15,}&" +
"redirect_uri=http://localhost/login/oauth2/code/registration-id");
}
@Test
@@ -243,7 +252,10 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?response_type=code&client_id=client-id&scope=read%3Auser&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Fauthorize%2Foauth2%2Fcode%2Fregistration-id");
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id&" +
"scope=read:user&state=.{15,}&" +
"redirect_uri=http://localhost/authorize/oauth2/code/registration-id");
verify(this.requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@@ -298,7 +310,11 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
verifyZeroInteractions(filterChain);
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?response_type=code&client_id=client-id&scope=read%3Auser&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fregistration-id&idp=https%3A%2F%2Fother.provider.com");
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id&" +
"scope=read:user&state=.{15,}&" +
"redirect_uri=http://localhost/login/oauth2/code/registration-id&" +
"idp=https://other.provider.com");
}
// gh-4911, gh-5244
@@ -339,6 +355,10 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
verifyZeroInteractions(filterChain);
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?response_type=code&client_id=client-id&scope=read%3Auser&state=.{15,}&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fregistration-id&login_hint=user@provider\\.com");
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id&" +
"scope=read:user&state=.{15,}&" +
"redirect_uri=http://localhost/login/oauth2/code/registration-id&" +
"login_hint=user@provider\\.com");
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@ package org.springframework.security.oauth2.client.web.reactive.function.client;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.core.codec.ByteBufferEncoder;
@@ -89,6 +91,9 @@ public class ServerOAuth2AuthorizedClientExchangeFilterFunctionTests {
@Mock
private ServerWebExchange serverWebExchange;
@Captor
private ArgumentCaptor<OAuth2AuthorizedClient> authorizedClientCaptor;
private ServerOAuth2AuthorizedClientExchangeFilterFunction function;
private MockExchangeFunction exchange = new MockExchangeFunction();
@@ -173,7 +178,62 @@ public class ServerOAuth2AuthorizedClientExchangeFilterFunctionTests {
.subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication))
.block();
verify(this.authorizedClientRepository).saveAuthorizedClient(any(), eq(authentication), any());
verify(this.authorizedClientRepository).saveAuthorizedClient(this.authorizedClientCaptor.capture(), eq(authentication), any());
OAuth2AuthorizedClient newAuthorizedClient = authorizedClientCaptor.getValue();
assertThat(newAuthorizedClient.getAccessToken()).isEqualTo(response.getAccessToken());
assertThat(newAuthorizedClient.getRefreshToken()).isEqualTo(response.getRefreshToken());
List<ClientRequest> requests = this.exchange.getRequests();
assertThat(requests).hasSize(2);
ClientRequest request0 = requests.get(0);
assertThat(request0.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic Y2xpZW50LWlkOmNsaWVudC1zZWNyZXQ=");
assertThat(request0.url().toASCIIString()).isEqualTo("https://example.com/login/oauth/access_token");
assertThat(request0.method()).isEqualTo(HttpMethod.POST);
assertThat(getBody(request0)).isEqualTo("grant_type=refresh_token&refresh_token=refresh-token");
ClientRequest request1 = requests.get(1);
assertThat(request1.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer token-1");
assertThat(request1.url().toASCIIString()).isEqualTo("https://example.com");
assertThat(request1.method()).isEqualTo(HttpMethod.GET);
assertThat(getBody(request1)).isEmpty();
}
@Test
public void filterWhenRefreshRequiredThenRefreshAndResponseDoesNotContainRefreshToken() {
when(this.authorizedClientRepository.saveAuthorizedClient(any(), any(), any())).thenReturn(Mono.empty());
OAuth2AccessTokenResponse response = OAuth2AccessTokenResponse.withToken("token-1")
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.expiresIn(3600)
// .refreshToken(xxx) // No refreshToken in response
.build();
when(this.exchange.getResponse().body(any())).thenReturn(Mono.just(response));
Instant issuedAt = Instant.now().minus(Duration.ofDays(1));
Instant accessTokenExpiresAt = issuedAt.plus(Duration.ofHours(1));
this.accessToken = new OAuth2AccessToken(this.accessToken.getTokenType(),
this.accessToken.getTokenValue(),
issuedAt,
accessTokenExpiresAt);
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", issuedAt);
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.registration,
"principalName", this.accessToken, refreshToken);
ClientRequest request = ClientRequest.create(GET, URI.create("https://example.com"))
.attributes(oauth2AuthorizedClient(authorizedClient))
.build();
TestingAuthenticationToken authentication = new TestingAuthenticationToken("test", "this");
this.function.filter(request, this.exchange)
.subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication))
.block();
verify(this.authorizedClientRepository).saveAuthorizedClient(this.authorizedClientCaptor.capture(), eq(authentication), any());
OAuth2AuthorizedClient newAuthorizedClient = authorizedClientCaptor.getValue();
assertThat(newAuthorizedClient.getAccessToken()).isEqualTo(response.getAccessToken());
assertThat(newAuthorizedClient.getRefreshToken()).isEqualTo(authorizedClient.getRefreshToken());
List<ClientRequest> requests = this.exchange.getRequests();
assertThat(requests).hasSize(2);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -74,13 +74,11 @@ import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.*;
@@ -100,6 +98,8 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
private WebClient.RequestHeadersSpec<?> spec;
@Captor
private ArgumentCaptor<Consumer<Map<String, Object>>> attrs;
@Captor
private ArgumentCaptor<OAuth2AuthorizedClient> authorizedClientCaptor;
/**
* Used for get the attributes from defaultRequest.
@@ -405,7 +405,61 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
this.function.filter(request, this.exchange).block();
verify(this.authorizedClientRepository).saveAuthorizedClient(any(), eq(this.authentication), any(), any());
verify(this.authorizedClientRepository).saveAuthorizedClient(this.authorizedClientCaptor.capture(), eq(this.authentication), any(), any());
OAuth2AuthorizedClient newAuthorizedClient = authorizedClientCaptor.getValue();
assertThat(newAuthorizedClient.getAccessToken()).isEqualTo(response.getAccessToken());
assertThat(newAuthorizedClient.getRefreshToken()).isEqualTo(response.getRefreshToken());
List<ClientRequest> requests = this.exchange.getRequests();
assertThat(requests).hasSize(2);
ClientRequest request0 = requests.get(0);
assertThat(request0.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic Y2xpZW50LWlkOmNsaWVudC1zZWNyZXQ=");
assertThat(request0.url().toASCIIString()).isEqualTo("https://example.com/login/oauth/access_token");
assertThat(request0.method()).isEqualTo(HttpMethod.POST);
assertThat(getBody(request0)).isEqualTo("grant_type=refresh_token&refresh_token=refresh-token");
ClientRequest request1 = requests.get(1);
assertThat(request1.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer token-1");
assertThat(request1.url().toASCIIString()).isEqualTo("https://example.com");
assertThat(request1.method()).isEqualTo(HttpMethod.GET);
assertThat(getBody(request1)).isEmpty();
}
@Test
public void filterWhenRefreshRequiredThenRefreshAndResponseDoesNotContainRefreshToken() {
OAuth2AccessTokenResponse response = OAuth2AccessTokenResponse.withToken("token-1")
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.expiresIn(3600)
// .refreshToken(xxx) // No refreshToken in response
.build();
when(this.exchange.getResponse().body(any())).thenReturn(Mono.just(response));
Instant issuedAt = Instant.now().minus(Duration.ofDays(1));
Instant accessTokenExpiresAt = issuedAt.plus(Duration.ofHours(1));
this.accessToken = new OAuth2AccessToken(this.accessToken.getTokenType(),
this.accessToken.getTokenValue(),
issuedAt,
accessTokenExpiresAt);
this.function = new ServletOAuth2AuthorizedClientExchangeFilterFunction(this.clientRegistrationRepository,
this.authorizedClientRepository);
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", issuedAt);
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.registration,
"principalName", this.accessToken, refreshToken);
ClientRequest request = ClientRequest.create(GET, URI.create("https://example.com"))
.attributes(oauth2AuthorizedClient(authorizedClient))
.attributes(authentication(this.authentication))
.build();
this.function.filter(request, this.exchange).block();
verify(this.authorizedClientRepository).saveAuthorizedClient(this.authorizedClientCaptor.capture(), eq(this.authentication), any(), any());
OAuth2AuthorizedClient newAuthorizedClient = authorizedClientCaptor.getValue();
assertThat(newAuthorizedClient.getAccessToken()).isEqualTo(response.getAccessToken());
assertThat(newAuthorizedClient.getRefreshToken()).isEqualTo(refreshToken);
List<ClientRequest> requests = this.exchange.getRequests();
assertThat(requests).hasSize(2);
@@ -516,6 +570,121 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
assertThat(getBody(request0)).isEmpty();
}
// gh-6483
@Test
public void filterWhenChainedThenDefaultsStillAvailable() throws Exception {
this.function = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
this.clientRegistrationRepository, this.authorizedClientRepository);
this.function.afterPropertiesSet(); // Hooks.onLastOperator() initialized
this.function.setDefaultOAuth2AuthorizedClient(true);
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(servletRequest, servletResponse));
OAuth2User user = mock(OAuth2User.class);
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
OAuth2AuthenticationToken authentication = new OAuth2AuthenticationToken(
user, authorities, this.registration.getRegistrationId());
SecurityContextHolder.getContext().setAuthentication(authentication);
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
this.registration, "principalName", this.accessToken);
when(this.authorizedClientRepository.loadAuthorizedClient(eq(authentication.getAuthorizedClientRegistrationId()),
eq(authentication), eq(servletRequest))).thenReturn(authorizedClient);
// Default request attributes set
final ClientRequest request1 = ClientRequest.create(GET, URI.create("https://example1.com"))
.attributes(attrs -> attrs.putAll(getDefaultRequestAttributes())).build();
// Default request attributes NOT set
final ClientRequest request2 = ClientRequest.create(GET, URI.create("https://example2.com")).build();
this.function.filter(request1, this.exchange)
.flatMap(response -> this.function.filter(request2, this.exchange))
.block();
this.function.destroy(); // Hooks.onLastOperator() released
List<ClientRequest> requests = this.exchange.getRequests();
assertThat(requests).hasSize(2);
ClientRequest request = requests.get(0);
assertThat(request.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer token-0");
assertThat(request.url().toASCIIString()).isEqualTo("https://example1.com");
assertThat(request.method()).isEqualTo(HttpMethod.GET);
assertThat(getBody(request)).isEmpty();
request = requests.get(1);
assertThat(request.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer token-0");
assertThat(request.url().toASCIIString()).isEqualTo("https://example2.com");
assertThat(request.method()).isEqualTo(HttpMethod.GET);
assertThat(getBody(request)).isEmpty();
}
@Test
public void filterWhenRequestAttributesNotSetAndHooksNotInitThenDefaultsNotAvailable() throws Exception {
this.function = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
this.clientRegistrationRepository, this.authorizedClientRepository);
// this.function.afterPropertiesSet(); // Hooks.onLastOperator() NOT initialized
this.function.setDefaultOAuth2AuthorizedClient(true);
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(servletRequest, servletResponse));
OAuth2User user = mock(OAuth2User.class);
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
OAuth2AuthenticationToken authentication = new OAuth2AuthenticationToken(
user, authorities, this.registration.getRegistrationId());
SecurityContextHolder.getContext().setAuthentication(authentication);
ClientRequest request = ClientRequest.create(GET, URI.create("https://example.com")).build();
this.function.filter(request, this.exchange).block();
List<ClientRequest> requests = this.exchange.getRequests();
assertThat(requests).hasSize(1);
request = requests.get(0);
assertThat(request.headers().getFirst(HttpHeaders.AUTHORIZATION)).isNull();
assertThat(request.url().toASCIIString()).isEqualTo("https://example.com");
assertThat(request.method()).isEqualTo(HttpMethod.GET);
assertThat(getBody(request)).isEmpty();
}
@Test
public void filterWhenRequestAttributesNotSetAndHooksInitHooksResetThenDefaultsNotAvailable() throws Exception {
this.function = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
this.clientRegistrationRepository, this.authorizedClientRepository);
this.function.afterPropertiesSet(); // Hooks.onLastOperator() initialized
this.function.destroy(); // Hooks.onLastOperator() released
this.function.setDefaultOAuth2AuthorizedClient(true);
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(servletRequest, servletResponse));
OAuth2User user = mock(OAuth2User.class);
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
OAuth2AuthenticationToken authentication = new OAuth2AuthenticationToken(
user, authorities, this.registration.getRegistrationId());
SecurityContextHolder.getContext().setAuthentication(authentication);
ClientRequest request = ClientRequest.create(GET, URI.create("https://example.com")).build();
this.function.filter(request, this.exchange).block();
List<ClientRequest> requests = this.exchange.getRequests();
assertThat(requests).hasSize(1);
request = requests.get(0);
assertThat(request.headers().getFirst(HttpHeaders.AUTHORIZATION)).isNull();
assertThat(request.url().toASCIIString()).isEqualTo("https://example.com");
assertThat(request.method()).isEqualTo(HttpMethod.GET);
assertThat(getBody(request)).isEmpty();
}
private static String getBody(ClientRequest request) {
final List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
messageWriters.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
@@ -77,7 +77,10 @@ public class DefaultServerOAuth2AuthorizationRequestResolverTests {
OAuth2AuthorizationRequest request = resolve("/oauth2/authorization/not-found-id");
assertThat(request.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?response_type=code&client_id=client-id&scope=read%3Auser&state=.*?&redirect_uri=%2Flogin%2Foauth2%2Fcode%2Fregistration-id");
assertThat(request.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id&" +
"scope=read:user&state=.*?&" +
"redirect_uri=/login/oauth2/code/registration-id");
}
private OAuth2AuthorizationRequest resolve(String path) {
@@ -3,6 +3,7 @@ apply plugin: 'io.spring.convention.spring-module'
dependencies {
compile project(':spring-security-core')
compile springCoreDependency
compile 'org.springframework:spring-web'
optional 'com.fasterxml.jackson.core:jackson-databind'
optional 'com.nimbusds:oauth2-oidc-sdk'
@@ -15,24 +15,25 @@
*/
package org.springframework.security.oauth2.core.endpoint;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
/**
* A representation of an OAuth 2.0 Authorization Request
* for the authorization code grant type or implicit grant type.
@@ -336,34 +337,30 @@ public final class OAuth2AuthorizationRequest implements Serializable {
}
private String buildAuthorizationRequestUri() {
Map<String, String> parameters = new LinkedHashMap<>();
parameters.put(OAuth2ParameterNames.RESPONSE_TYPE, this.responseType.getValue());
parameters.put(OAuth2ParameterNames.CLIENT_ID, this.clientId);
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, this.responseType.getValue());
parameters.set(OAuth2ParameterNames.CLIENT_ID, this.clientId);
if (!CollectionUtils.isEmpty(this.scopes)) {
parameters.put(OAuth2ParameterNames.SCOPE,
parameters.set(OAuth2ParameterNames.SCOPE,
StringUtils.collectionToDelimitedString(this.scopes, " "));
}
if (this.state != null) {
parameters.put(OAuth2ParameterNames.STATE, this.state);
parameters.set(OAuth2ParameterNames.STATE, this.state);
}
if (this.redirectUri != null) {
parameters.put(OAuth2ParameterNames.REDIRECT_URI, this.redirectUri);
parameters.set(OAuth2ParameterNames.REDIRECT_URI, this.redirectUri);
}
if (!CollectionUtils.isEmpty(this.additionalParameters)) {
this.additionalParameters.entrySet().stream()
.filter(e -> !e.getKey().equals(OAuth2ParameterNames.REGISTRATION_ID))
.forEach(e -> parameters.put(e.getKey(), e.getValue().toString()));
.forEach(e -> parameters.set(e.getKey(), e.getValue().toString()));
}
try {
StringJoiner queryParams = new StringJoiner("&");
for (String paramName : parameters.keySet()) {
queryParams.add(paramName + "=" + URLEncoder.encode(parameters.get(paramName), "UTF-8"));
}
return this.authorizationUri + "?" + queryParams.toString();
} catch (UnsupportedEncodingException ex) {
throw new IllegalArgumentException("Unable to build authorization request uri: " + ex.getMessage(), ex);
}
return UriComponentsBuilder.fromHttpUrl(this.authorizationUri)
.queryParams(parameters)
.encode(StandardCharsets.UTF_8)
.build()
.toUriString();
}
}
}
@@ -55,15 +55,15 @@ class OAuth2AccessTokenResponseBodyExtractor
@Override
public Mono<OAuth2AccessTokenResponse> extract(ReactiveHttpInputMessage inputMessage,
Context context) {
ParameterizedTypeReference<Map<String, String>> type = new ParameterizedTypeReference<Map<String, String>>() {};
BodyExtractor<Mono<Map<String, String>>, ReactiveHttpInputMessage> delegate = BodyExtractors.toMono(type);
ParameterizedTypeReference<Map<String, Object>> type = new ParameterizedTypeReference<Map<String, Object>>() {};
BodyExtractor<Mono<Map<String, Object>>, ReactiveHttpInputMessage> delegate = BodyExtractors.toMono(type);
return delegate.extract(inputMessage, context)
.map(json -> parse(json))
.map(OAuth2AccessTokenResponseBodyExtractor::parse)
.flatMap(OAuth2AccessTokenResponseBodyExtractor::oauth2AccessTokenResponse)
.map(OAuth2AccessTokenResponseBodyExtractor::oauth2AccessTokenResponse);
}
private static TokenResponse parse(Map<String, String> json) {
private static TokenResponse parse(Map<String, Object> json) {
try {
return TokenResponse.parse(new JSONObject(json));
}
@@ -15,16 +15,19 @@
*/
package org.springframework.security.oauth2.core.endpoint;
import org.junit.Test;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import static org.assertj.core.api.Assertions.*;
import org.junit.Test;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link OAuth2AuthorizationRequest}.
@@ -194,7 +197,11 @@ public class OAuth2AuthorizationRequestTests {
.state(STATE)
.build();
assertThat(authorizationRequest.getAuthorizationRequestUri()).isEqualTo("https://provider.com/oauth2/authorize?response_type=token&client_id=client-id&scope=scope1+scope2&state=state&redirect_uri=http%3A%2F%2Fexample.com");
assertThat(authorizationRequest.getAuthorizationRequestUri())
.isEqualTo("https://provider.com/oauth2/authorize?" +
"response_type=token&client_id=client-id&" +
"scope=scope1%20scope2&state=state&" +
"redirect_uri=http://example.com");
}
@Test
@@ -226,7 +233,11 @@ public class OAuth2AuthorizationRequestTests {
.build();
assertThat(authorizationRequest.getAuthorizationRequestUri()).isNotNull();
assertThat(authorizationRequest.getAuthorizationRequestUri()).isEqualTo("https://provider.com/oauth2/authorize?response_type=code&client_id=client-id&scope=scope1+scope2&state=state&redirect_uri=http%3A%2F%2Fexample.com&param1=value1&param2=value2");
assertThat(authorizationRequest.getAuthorizationRequestUri())
.isEqualTo("https://provider.com/oauth2/authorize?" +
"response_type=code&client_id=client-id&" +
"scope=scope1%20scope2&state=state&" +
"redirect_uri=http://example.com&param1=value1&param2=value2");
}
@Test
@@ -248,13 +259,17 @@ public class OAuth2AuthorizationRequestTests {
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri(AUTHORIZATION_URI)
.clientId(CLIENT_ID)
.redirectUri(REDIRECT_URI)
.redirectUri(REDIRECT_URI + "?rparam1=rvalue1&rparam2=rvalue2")
.scopes(SCOPES)
.state(STATE)
.additionalParameters(additionalParameters)
.build();
assertThat(authorizationRequest.getAuthorizationRequestUri()).isEqualTo("https://provider.com/oauth2/authorize?response_type=code&client_id=client-id&scope=scope1+scope2&state=state&redirect_uri=http%3A%2F%2Fexample.com&param1=value1");
assertThat(authorizationRequest.getAuthorizationRequestUri())
.isEqualTo("https://provider.com/oauth2/authorize?" +
"response_type=code&client_id=client-id&" +
"scope=scope1%20scope2&state=state&" +
"redirect_uri=http://example.com?rparam1%3Drvalue1%26rparam2%3Drvalue2&param1=value1");
}
@Test
@@ -290,4 +305,19 @@ public class OAuth2AuthorizationRequestTests {
assertThat(authorizationRequestCopy.getAdditionalParameters()).isEqualTo(authorizationRequest.getAdditionalParameters());
assertThat(authorizationRequestCopy.getAuthorizationRequestUri()).isEqualTo(authorizationRequest.getAuthorizationRequestUri());
}
@Test
public void buildWhenAuthorizationUriIncludesQueryParameterThenAuthorizationRequestUrlIncludesIt() {
OAuth2AuthorizationRequest authorizationRequest =
TestOAuth2AuthorizationRequests.request()
.authorizationUri(AUTHORIZATION_URI +
"?param1=value1&param2=value2").build();
assertThat(authorizationRequest.getAuthorizationRequestUri()).isNotNull();
assertThat(authorizationRequest.getAuthorizationRequestUri())
.isEqualTo("https://provider.com/oauth2/authorize?" +
"param1=value1&param2=value2&" +
"response_type=code&client_id=client-id&state=state&" +
"redirect_uri=https://example.com/authorize/oauth2/code/registration-id");
}
}
@@ -120,4 +120,33 @@ public class OAuth2BodyExtractorsTests {
assertThat(result.getRefreshToken().getTokenValue()).isEqualTo("tGzv3JOkF0XG5Qx2TlKWIA");
assertThat(result.getAdditionalParameters()).containsEntry("example_parameter", "example_value");
}
@Test
// gh-6087
public void oauth2AccessTokenResponseWhenMultipleAttributeTypesThenCreated() throws Exception {
BodyExtractor<Mono<OAuth2AccessTokenResponse>, ReactiveHttpInputMessage> extractor = OAuth2BodyExtractors
.oauth2AccessTokenResponse();
MockClientHttpResponse response = new MockClientHttpResponse(HttpStatus.OK);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
response.setBody("{\n"
+ " \"access_token\":\"2YotnFZFEjr1zCsicMWpAA\",\n"
+ " \"token_type\":\"Bearer\",\n"
+ " \"expires_in\":3600,\n"
+ " \"refresh_token\":\"tGzv3JOkF0XG5Qx2TlKWIA\",\n"
+ " \"subjson\":{}, \n"
+ " \"list\":[] \n"
+ " }");
Instant now = Instant.now();
OAuth2AccessTokenResponse result = extractor.extract(response, this.context).block();
assertThat(result.getAccessToken().getTokenValue()).isEqualTo("2YotnFZFEjr1zCsicMWpAA");
assertThat(result.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER);
assertThat(result.getAccessToken().getExpiresAt()).isBetween(now.plusSeconds(3600), now.plusSeconds(3600 + 2));
assertThat(result.getRefreshToken().getTokenValue()).isEqualTo("tGzv3JOkF0XG5Qx2TlKWIA");
assertThat(result.getAdditionalParameters().get("subjson")).isInstanceOfAny(Map.class);
assertThat(result.getAdditionalParameters().get("list")).isInstanceOfAny(List.class);
}
}
@@ -15,9 +15,6 @@
*/
package org.springframework.security.oauth2.jwt;
import java.net.MalformedURLException;
import java.net.URL;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
@@ -37,7 +34,7 @@ public final class JwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
"This iss claim is not equal to the configured issuer",
"https://tools.ietf.org/html/rfc6750#section-3.1");
private final URL issuer;
private final String issuer;
/**
* Constructs a {@link JwtIssuerValidator} using the provided parameters
@@ -46,14 +43,7 @@ public final class JwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
*/
public JwtIssuerValidator(String issuer) {
Assert.notNull(issuer, "issuer cannot be null");
try {
this.issuer = new URL(issuer);
} catch (MalformedURLException ex) {
throw new IllegalArgumentException(
"Invalid Issuer URL " + issuer + " : " + ex.getMessage(),
ex);
}
this.issuer = issuer;
}
/**
@@ -63,7 +53,8 @@ public final class JwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
public OAuth2TokenValidatorResult validate(Jwt token) {
Assert.notNull(token, "token cannot be null");
if (this.issuer.equals(token.getIssuer())) {
String tokenIssuer = token.getClaimAsString(JwtClaimNames.ISS);
if (this.issuer.equals(tokenIssuer)) {
return OAuth2TokenValidatorResult.success();
} else {
return OAuth2TokenValidatorResult.failure(INVALID_ISSUER);
@@ -16,7 +16,6 @@
package org.springframework.security.oauth2.jwt;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.time.Instant;
@@ -42,7 +41,7 @@ public final class MappedJwtClaimSetConverter
implements Converter<Map<String, Object>, Map<String, Object>> {
private static final Converter<Object, Collection<String>> AUDIENCE_CONVERTER = new AudienceConverter();
private static final Converter<Object, URL> ISSUER_CONVERTER = new IssuerConverter();
private static final Converter<Object, String> ISSUER_CONVERTER = new IssuerConverter();
private static final Converter<Object, String> STRING_CONVERTER = new StringConverter();
private static final Converter<Object, Instant> TEMPORAL_CONVERTER = new InstantConverter();
@@ -157,39 +156,27 @@ public final class MappedJwtClaimSetConverter
* Coerces an <a target="_blank" href="https://tools.ietf.org/html/rfc7519#section-4.1.1">Issuer</a> claim
* into a {@link URL}, ignoring null values, and throwing an error if its coercion efforts fail.
*/
private static class IssuerConverter implements Converter<Object, URL> {
private static class IssuerConverter implements Converter<Object, String> {
@Override
public URL convert(Object source) {
public String convert(Object source) {
if (source == null) {
return null;
}
if (source instanceof URL) {
return (URL) source;
return ((URL) source).toExternalForm();
}
if (source instanceof URI) {
return toUrl((URI) source);
if (source instanceof String && ((String) source).contains(":")) {
try {
return URI.create((String) source).toString();
} catch (Exception e) {
throw new IllegalStateException("Could not coerce " + source + " into a URI String", e);
}
}
return toUrl(source.toString());
}
private URL toUrl(URI source) {
try {
return source.toURL();
} catch (MalformedURLException e) {
throw new IllegalStateException("Could not coerce " + source + " into a URL", e);
}
}
private URL toUrl(String source) {
try {
return new URL(source);
} catch (MalformedURLException e) {
throw new IllegalStateException("Could not coerce " + source + " into a URL", e);
}
return source.toString();
}
}
@@ -23,9 +23,6 @@ import org.junit.Test;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
@@ -73,14 +70,36 @@ public class JwtIssuerValidatorTests {
}
@Test
public void validateWhenJwtIsNullThenThrowsIllegalArgumentException() {
assertThatCode(() -> this.validator.validate(null))
.isInstanceOf(IllegalArgumentException.class);
public void validateWhenJwtHasNoIssuerThenReturnsError() {
Jwt jwt = new Jwt(
MOCK_TOKEN,
MOCK_ISSUED_AT,
MOCK_EXPIRES_AT,
MOCK_HEADERS,
Collections.singletonMap(JwtClaimNames.AUD, "https://aud"));
OAuth2TokenValidatorResult result = this.validator.validate(jwt);
assertThat(result.getErrors()).isNotEmpty();
}
// gh-6073
@Test
public void validateWhenIssuerMatchesAndIsNotAUriThenReturnsSuccess() {
Jwt jwt = new Jwt(
MOCK_TOKEN,
MOCK_ISSUED_AT,
MOCK_EXPIRES_AT,
MOCK_HEADERS,
Collections.singletonMap(JwtClaimNames.ISS, "issuer"));
JwtIssuerValidator validator = new JwtIssuerValidator("issuer");
assertThat(validator.validate(jwt))
.isEqualTo(OAuth2TokenValidatorResult.success());
}
@Test
public void constructorWhenMalformedIssuerIsGivenThenThrowsIllegalArgumentException() {
assertThatCode(() -> new JwtIssuerValidator("issuer"))
public void validateWhenJwtIsNullThenThrowsIllegalArgumentException() {
assertThatCode(() -> this.validator.validate(null))
.isInstanceOf(IllegalArgumentException.class);
}
@@ -108,7 +108,7 @@ public class MappedJwtClaimSetConverterTests {
assertThat(target.get(JwtClaimNames.AUD)).isEqualTo(Arrays.asList("audience"));
assertThat(target.get(JwtClaimNames.EXP)).isEqualTo(Instant.ofEpochSecond(2000000000L));
assertThat(target.get(JwtClaimNames.IAT)).isEqualTo(Instant.ofEpochSecond(1000000000L));
assertThat(target.get(JwtClaimNames.ISS)).isEqualTo(new URL("https://any.url"));
assertThat(target.get(JwtClaimNames.ISS)).isEqualTo("https://any.url");
assertThat(target.get(JwtClaimNames.NBF)).isEqualTo(Instant.ofEpochSecond(1000000000L));
assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234");
}
@@ -135,7 +135,7 @@ public class MappedJwtClaimSetConverterTests {
assertThat(target.get(JwtClaimNames.AUD)).isEqualTo(Arrays.asList("audience"));
assertThat(target.get(JwtClaimNames.EXP)).isEqualTo(Instant.ofEpochSecond(2000000000L));
assertThat(target.get(JwtClaimNames.IAT)).isEqualTo(Instant.ofEpochSecond(1000000000L));
assertThat(target.get(JwtClaimNames.ISS)).isEqualTo(new URL("https://any.url"));
assertThat(target.get(JwtClaimNames.ISS)).isEqualTo("https://any.url");
assertThat(target.get(JwtClaimNames.NBF)).isEqualTo(Instant.ofEpochSecond(1000000000L));
assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234");
}
@@ -196,7 +196,7 @@ public class MappedJwtClaimSetConverterTests {
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
.withDefaults(Collections.emptyMap());
Map<String, Object> badIssuer = Collections.singletonMap(JwtClaimNames.ISS, "badly-formed-iss");
Map<String, Object> badIssuer = Collections.singletonMap(JwtClaimNames.ISS, "https://badly formed iss");
assertThatCode(() -> converter.convert(badIssuer)).isInstanceOf(IllegalStateException.class);
Map<String, Object> badIssuedAt = Collections.singletonMap(JwtClaimNames.IAT, "badly-formed-iat");
@@ -209,6 +209,28 @@ public class MappedJwtClaimSetConverterTests {
assertThatCode(() -> converter.convert(badNotBefore)).isInstanceOf(IllegalStateException.class);
}
// gh-6073
@Test
public void convertWhenIssuerIsNotAUriThenConvertsToString() {
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
.withDefaults(Collections.emptyMap());
Map<String, Object> nonUriIssuer = Collections.singletonMap(JwtClaimNames.ISS, "issuer");
Map<String, Object> target = converter.convert(nonUriIssuer);
assertThat(target.get(JwtClaimNames.ISS)).isEqualTo("issuer");
}
// gh-6073
@Test
public void convertWhenIssuerIsOfTypeURLThenConvertsToString() throws Exception {
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
.withDefaults(Collections.emptyMap());
Map<String, Object> issuer = Collections.singletonMap(JwtClaimNames.ISS, new URL("https://issuer"));
Map<String, Object> target = converter.convert(issuer);
assertThat(target.get(JwtClaimNames.ISS)).isEqualTo("https://issuer");
}
@Test
public void constructWhenAnyParameterIsNullThenIllegalArgumentException() {
assertThatCode(() -> new MappedJwtClaimSetConverter(null))
@@ -1,13 +1,11 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
ext['thymeleaf.version'] = '3.0.9.RELEASE'
dependencies {
compile project(':spring-security-config')
compile project(':spring-security-web')
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4'
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
testCompile project(':spring-security-test')
testCompile 'org.springframework.boot:spring-boot-starter-test'
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<title>Hello Spring Security</title>
<meta charset="utf-8" />
@@ -1,7 +1,5 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
ext['thymeleaf.version'] = '3.0.9.RELEASE'
dependencies {
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
compile 'org.springframework.boot:spring-boot-starter-web'
@@ -1,14 +1,12 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
ext['thymeleaf.version'] = '3.0.9.RELEASE'
dependencies {
compile project(':spring-security-config')
compile project(':spring-security-oauth2-client')
compile project(':spring-security-oauth2-jose')
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
compile 'org.springframework.boot:spring-boot-starter-webflux'
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4'
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
testCompile project(':spring-security-test')
testCompile 'net.sourceforge.htmlunit:htmlunit'
@@ -1,14 +1,12 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
ext['thymeleaf.version'] = '3.0.9.RELEASE'
dependencies {
compile project(':spring-security-config')
compile project(':spring-security-oauth2-client')
compile project(':spring-security-oauth2-jose')
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4'
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
testCompile project(':spring-security-test')
testCompile 'net.sourceforge.htmlunit:htmlunit'
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<title>Spring Security - OAuth 2.0 Login</title>
<meta charset="utf-8" />
@@ -40,7 +40,7 @@ spring:
client-id: replace-with-client-id
client-secret: replace-with-client-secret
provider: github
scopes: read:user,public_repo
scope: read:user,public_repo
----
+
.OAuth Client properties
@@ -6,7 +6,7 @@ dependencies {
compile project(':spring-security-oauth2-jose')
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
compile 'org.springframework.boot:spring-boot-starter-webflux'
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4'
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
compile 'io.projectreactor.netty:reactor-netty'
testCompile project(':spring-security-test')
@@ -16,6 +16,6 @@ spring:
client-id: replace-with-client-id
client-secret: replace-with-client-secret
provider: github
scopes: read:user,public_repo
scope: read:user,public_repo
resource-uri: https://api.github.com/user/repos
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<title>OAuth2 WebClient Showcase</title>
<meta charset="utf-8" />
+1 -1
View File
@@ -40,7 +40,7 @@ spring:
client-id: replace-with-client-id
client-secret: replace-with-client-secret
provider: github
scopes: read:user,public_repo
scope: read:user,public_repo
----
+
.OAuth Client properties
@@ -1,7 +1,5 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
ext['thymeleaf.version'] = '3.0.9.RELEASE'
dependencies {
compile project(':spring-security-config')
compile project(':spring-security-oauth2-client')
@@ -9,7 +7,7 @@ dependencies {
compile 'org.springframework:spring-webflux'
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4'
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
compile 'io.projectreactor.netty:reactor-netty'
testCompile project(':spring-security-test')
@@ -16,6 +16,6 @@ spring:
client-id: replace-with-client-id
client-secret: replace-with-client-secret
provider: github
scopes: read:user,public_repo
scope: read:user,public_repo
resource-uri: https://api.github.com/user/repos
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<title>OAuth2 WebClient Showcase</title>
<meta charset="utf-8" />
@@ -22,6 +22,8 @@ dependencies {
compile project(':spring-security-web')
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
compile 'org.springframework.boot:spring-boot-starter-webflux'
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.0.4.RELEASE'
testCompile project(':spring-security-test')
testCompile 'org.springframework.boot:spring-boot-starter-test'

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