Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 114e00f1dd | |||
| a0e541eb70 | |||
| 477a5a7cd3 | |||
| 8918a7e693 | |||
| bf33f9acef | |||
| f1598b985c | |||
| cc1ab2c368 | |||
| aac4525737 | |||
| eeb099a04f | |||
| 3e90c7663f | |||
| cfec514082 | |||
| 80b123bf36 | |||
| ee6f9e0b92 | |||
| 2625de9c23 | |||
| a145653030 | |||
| 8bc0ef86da | |||
| a02d6ad2a4 | |||
| acdd9ba1db | |||
| e218ac2e40 | |||
| dd859c4a6e | |||
| 5d85a1c9d8 | |||
| 49167458d2 | |||
| 6b4edacde2 | |||
| b7cebee89e | |||
| 01787ea168 | |||
| e3e9758da7 | |||
| 46012d62af | |||
| 6eb8129173 | |||
| 55dd732443 | |||
| 45cbdc03ba | |||
| d99c7dbfd1 | |||
| d8de4d06f7 | |||
| be2f0f0fae | |||
| 920d5ca6ad | |||
| 6dcf5a27b1 | |||
| c2166c4207 | |||
| 1542835268 | |||
| ae471e1456 | |||
| 853c54eecb | |||
| 10f6d10f81 | |||
| 02d844d528 | |||
| c0849ba891 | |||
| db145fa03f | |||
| 42b8b794a8 | |||
| 8014114225 | |||
| a742c0c3f2 | |||
| 1cec4fe3ac | |||
| 8655caa2de | |||
| dc5f5f348d | |||
| 35e05780ea | |||
| a461abcf32 | |||
| ba1f5f3873 | |||
| 5478b74116 | |||
| dc4aa3d017 | |||
| 2bc156ed19 | |||
| 0270994340 | |||
| a557a324c0 | |||
| 73022059d4 | |||
| 165cbcb723 | |||
| d99ecc2a7b | |||
| ccd2664e6c | |||
| b87ff3c00f | |||
| 8971777908 | |||
| 3d618d78ac | |||
| 697b8cd734 | |||
| cf03faf58e | |||
| 13159e9f88 | |||
| 41b7c74928 | |||
| 0a4ac4dbfc | |||
| 1eb56f46a5 | |||
| cfc3c25304 | |||
| 03a3a3b770 |
+1
-1
@@ -47,7 +47,7 @@ Create your topic branch to be submitted as a pull request from master. The Spri
|
|||||||
# Use short branch names
|
# 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.
|
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.
|
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
@@ -30,7 +30,11 @@ try {
|
|||||||
checkout scm
|
checkout scm
|
||||||
withCredentials([string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')]) {
|
withCredentials([string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')]) {
|
||||||
try {
|
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) {
|
} catch(Exception e) {
|
||||||
currentBuild.result = 'FAILED: sonar'
|
currentBuild.result = 'FAILED: sonar'
|
||||||
throw e
|
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') {
|
if(currentBuild.result == 'SUCCESS') {
|
||||||
@@ -106,6 +140,9 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
currentBuild.result = 'FAILED: deploys'
|
||||||
|
throw e
|
||||||
} finally {
|
} finally {
|
||||||
def buildStatus = currentBuild.result
|
def buildStatus = currentBuild.result
|
||||||
def buildNotSuccess = !SUCCESS.equals(buildStatus)
|
def buildNotSuccess = !SUCCESS.equals(buildStatus)
|
||||||
|
|||||||
+2
-16
@@ -1,10 +1,11 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
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"
|
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
maven { url 'https://repo.spring.io/plugins-snapshot' }
|
maven { url 'https://repo.spring.io/plugins-snapshot' }
|
||||||
|
maven { url 'https://plugins.gradle.org/m2/' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
apply plugin: 'io.spring.convention.root'
|
apply plugin: 'io.spring.convention.root'
|
||||||
@@ -18,23 +19,8 @@ ext.milestoneBuild = !(snapshotBuild || releaseBuild)
|
|||||||
|
|
||||||
dependencyManagementExport.projects = subprojects.findAll { !it.name.contains('-boot') }
|
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 {
|
subprojects {
|
||||||
plugins.withType(JavaPlugin) {
|
plugins.withType(JavaPlugin) {
|
||||||
project.sourceCompatibility='1.8'
|
project.sourceCompatibility='1.8'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
@@ -200,6 +200,7 @@ public abstract class WebSecurityConfigurerAdapter implements
|
|||||||
|
|
||||||
AuthenticationManager authenticationManager = authenticationManager();
|
AuthenticationManager authenticationManager = authenticationManager();
|
||||||
authenticationBuilder.parentAuthenticationManager(authenticationManager);
|
authenticationBuilder.parentAuthenticationManager(authenticationManager);
|
||||||
|
authenticationBuilder.authenticationEventPublisher(eventPublisher);
|
||||||
Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
|
Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
|
||||||
|
|
||||||
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
|
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
|
||||||
|
|||||||
+7
@@ -25,6 +25,7 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
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.RequestCache;
|
||||||
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
||||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||||
@@ -87,6 +88,12 @@ public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>> exte
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public H disable() {
|
||||||
|
getBuilder().setSharedObject(RequestCache.class, new NullRequestCache());
|
||||||
|
return super.disable();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(H http) throws Exception {
|
public void init(H http) throws Exception {
|
||||||
http.setSharedObject(RequestCache.class, getRequestCache(http));
|
http.setSharedObject(RequestCache.class, getRequestCache(http));
|
||||||
|
|||||||
+3
-3
@@ -183,9 +183,9 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
|||||||
if ( this.jwtConfigurer == null ) {
|
if ( this.jwtConfigurer == null ) {
|
||||||
throw new IllegalStateException("Jwt is the only supported format for bearer tokens " +
|
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 " +
|
"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 " +
|
"a jwk set uri by doing http.oauth2ResourceServer().jwt().jwkSetUri(uri), or wire a " +
|
||||||
"JwtDecoder instance by doing http.oauth2().resourceServer().jwt().decoder(decoder), or " +
|
"JwtDecoder instance by doing http.oauth2ResourceServer().jwt().decoder(decoder), or " +
|
||||||
"expose a JwtDecoder instance as a bean and do http.oauth2().resourceServer().jwt().");
|
"expose a JwtDecoder instance as a bean and do http.oauth2ResourceServer().jwt().");
|
||||||
}
|
}
|
||||||
|
|
||||||
JwtDecoder decoder = this.jwtConfigurer.getJwtDecoder();
|
JwtDecoder decoder = this.jwtConfigurer.getJwtDecoder();
|
||||||
|
|||||||
+1
-1
@@ -739,7 +739,7 @@ hsts-options.attlist &=
|
|||||||
## Specifies if subdomains should be included. Default true.
|
## Specifies if subdomains should be included. Default true.
|
||||||
attribute include-subdomains {xsd:boolean}?
|
attribute include-subdomains {xsd:boolean}?
|
||||||
hsts-options.attlist &=
|
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}?
|
attribute max-age-seconds {xsd:integer}?
|
||||||
hsts-options.attlist &=
|
hsts-options.attlist &=
|
||||||
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
||||||
|
|||||||
+1
-1
@@ -2299,7 +2299,7 @@
|
|||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
<xs:attribute name="max-age-seconds" type="xs:integer">
|
<xs:attribute name="max-age-seconds" type="xs:integer">
|
||||||
<xs:annotation>
|
<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.
|
Default one year.
|
||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
|
|||||||
+1
-1
@@ -759,7 +759,7 @@ hsts-options.attlist &=
|
|||||||
## Specifies if subdomains should be included. Default true.
|
## Specifies if subdomains should be included. Default true.
|
||||||
attribute include-subdomains {xsd:boolean}?
|
attribute include-subdomains {xsd:boolean}?
|
||||||
hsts-options.attlist &=
|
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}?
|
attribute max-age-seconds {xsd:integer}?
|
||||||
hsts-options.attlist &=
|
hsts-options.attlist &=
|
||||||
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
||||||
|
|||||||
+1
-1
@@ -2358,7 +2358,7 @@
|
|||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
<xs:attribute name="max-age-seconds" type="xs:integer">
|
<xs:attribute name="max-age-seconds" type="xs:integer">
|
||||||
<xs:annotation>
|
<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.
|
Default one year.
|
||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
|
|||||||
+1
-1
@@ -768,7 +768,7 @@ hsts-options.attlist &=
|
|||||||
## Specifies if subdomains should be included. Default true.
|
## Specifies if subdomains should be included. Default true.
|
||||||
attribute include-subdomains {xsd:boolean}?
|
attribute include-subdomains {xsd:boolean}?
|
||||||
hsts-options.attlist &=
|
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}?
|
attribute max-age-seconds {xsd:integer}?
|
||||||
hsts-options.attlist &=
|
hsts-options.attlist &=
|
||||||
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
||||||
|
|||||||
+1
-1
@@ -2387,7 +2387,7 @@
|
|||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
<xs:attribute name="max-age-seconds" type="xs:integer">
|
<xs:attribute name="max-age-seconds" type="xs:integer">
|
||||||
<xs:annotation>
|
<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.
|
Default one year.
|
||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
|
|||||||
+2
-2
@@ -748,7 +748,7 @@ csrf-options.attlist &=
|
|||||||
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
|
## 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 }?
|
attribute request-matcher-ref { xsd:token }?
|
||||||
csrf-options.attlist &=
|
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 }?
|
attribute token-repository-ref { xsd:token }?
|
||||||
|
|
||||||
headers =
|
headers =
|
||||||
@@ -770,7 +770,7 @@ hsts-options.attlist &=
|
|||||||
## Specifies if subdomains should be included. Default true.
|
## Specifies if subdomains should be included. Default true.
|
||||||
attribute include-subdomains {xsd:boolean}?
|
attribute include-subdomains {xsd:boolean}?
|
||||||
hsts-options.attlist &=
|
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}?
|
attribute max-age-seconds {xsd:integer}?
|
||||||
hsts-options.attlist &=
|
hsts-options.attlist &=
|
||||||
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
||||||
|
|||||||
+3
-2
@@ -2337,7 +2337,8 @@
|
|||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
<xs:attribute name="token-repository-ref" type="xs:token">
|
<xs:attribute name="token-repository-ref" type="xs:token">
|
||||||
<xs:annotation>
|
<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:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
@@ -2401,7 +2402,7 @@
|
|||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
<xs:attribute name="max-age-seconds" type="xs:integer">
|
<xs:attribute name="max-age-seconds" type="xs:integer">
|
||||||
<xs:annotation>
|
<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.
|
Default one year.
|
||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
|
|||||||
+2
-2
@@ -738,7 +738,7 @@ csrf-options.attlist &=
|
|||||||
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
|
## 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 }?
|
attribute request-matcher-ref { xsd:token }?
|
||||||
csrf-options.attlist &=
|
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 }?
|
attribute token-repository-ref { xsd:token }?
|
||||||
|
|
||||||
headers =
|
headers =
|
||||||
@@ -760,7 +760,7 @@ hsts-options.attlist &=
|
|||||||
## Specifies if subdomains should be included. Default true.
|
## Specifies if subdomains should be included. Default true.
|
||||||
attribute include-subdomains {xsd:boolean}?
|
attribute include-subdomains {xsd:boolean}?
|
||||||
hsts-options.attlist &=
|
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}?
|
attribute max-age-seconds {xsd:integer}?
|
||||||
hsts-options.attlist &=
|
hsts-options.attlist &=
|
||||||
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
||||||
|
|||||||
+3
-2
@@ -2232,7 +2232,8 @@
|
|||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
<xs:attribute name="token-repository-ref" type="xs:token">
|
<xs:attribute name="token-repository-ref" type="xs:token">
|
||||||
<xs:annotation>
|
<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:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
@@ -2296,7 +2297,7 @@
|
|||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
<xs:attribute name="max-age-seconds" type="xs:integer">
|
<xs:attribute name="max-age-seconds" type="xs:integer">
|
||||||
<xs:annotation>
|
<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.
|
Default one year.
|
||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
|
|||||||
+2
-2
@@ -738,7 +738,7 @@ csrf-options.attlist &=
|
|||||||
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
|
## 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 }?
|
attribute request-matcher-ref { xsd:token }?
|
||||||
csrf-options.attlist &=
|
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 }?
|
attribute token-repository-ref { xsd:token }?
|
||||||
|
|
||||||
headers =
|
headers =
|
||||||
@@ -760,7 +760,7 @@ hsts-options.attlist &=
|
|||||||
## Specifies if subdomains should be included. Default true.
|
## Specifies if subdomains should be included. Default true.
|
||||||
attribute include-subdomains {xsd:boolean}?
|
attribute include-subdomains {xsd:boolean}?
|
||||||
hsts-options.attlist &=
|
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}?
|
attribute max-age-seconds {xsd:integer}?
|
||||||
hsts-options.attlist &=
|
hsts-options.attlist &=
|
||||||
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
|
||||||
|
|||||||
+3
-2
@@ -2232,7 +2232,8 @@
|
|||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
<xs:attribute name="token-repository-ref" type="xs:token">
|
<xs:attribute name="token-repository-ref" type="xs:token">
|
||||||
<xs:annotation>
|
<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:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
@@ -2297,7 +2298,7 @@
|
|||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
<xs:attribute name="max-age-seconds" type="xs:integer">
|
<xs:attribute name="max-age-seconds" type="xs:integer">
|
||||||
<xs:annotation>
|
<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.
|
Default one year.
|
||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
|
|||||||
-209
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+13
-4
@@ -15,23 +15,31 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.config;
|
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.apache.commons.logging.Log;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||||
import org.powermock.modules.junit4.PowerMockRunner;
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
|
||||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
||||||
import org.springframework.messaging.Message;
|
import org.springframework.messaging.Message;
|
||||||
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
|
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
import org.springframework.util.ClassUtils;
|
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
|
* @author Luke Taylor
|
||||||
@@ -40,6 +48,7 @@ import org.springframework.util.ClassUtils;
|
|||||||
*/
|
*/
|
||||||
@RunWith(PowerMockRunner.class)
|
@RunWith(PowerMockRunner.class)
|
||||||
@PrepareForTest({ ClassUtils.class })
|
@PrepareForTest({ ClassUtils.class })
|
||||||
|
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*" })
|
||||||
public class SecurityNamespaceHandlerTests {
|
public class SecurityNamespaceHandlerTests {
|
||||||
@Rule
|
@Rule
|
||||||
public ExpectedException thrown = ExpectedException.none();
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|||||||
+7
-3
@@ -15,16 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.config.annotation.web;
|
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 java.util.Arrays;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||||
import org.powermock.modules.junit4.PowerMockRunner;
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
|
||||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
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.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.ConfigurableWebApplicationContext;
|
||||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
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
|
* @author Rob Winch
|
||||||
@@ -40,6 +43,7 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
|
|||||||
*/
|
*/
|
||||||
@RunWith(PowerMockRunner.class)
|
@RunWith(PowerMockRunner.class)
|
||||||
@PrepareForTest({ SpringFactoriesLoader.class })
|
@PrepareForTest({ SpringFactoriesLoader.class })
|
||||||
|
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*" })
|
||||||
public class WebSecurityConfigurerAdapterPowermockTests {
|
public class WebSecurityConfigurerAdapterPowermockTests {
|
||||||
ConfigurableWebApplicationContext context;
|
ConfigurableWebApplicationContext context;
|
||||||
|
|
||||||
|
|||||||
+11
-8
@@ -15,12 +15,22 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.config.annotation.web;
|
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.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||||
import org.powermock.modules.junit4.PowerMockRunner;
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationListener;
|
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.context.request.async.WebAsyncUtils;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
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.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
|
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
@@ -75,6 +77,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
*/
|
*/
|
||||||
@PrepareForTest({WebAsyncManager.class})
|
@PrepareForTest({WebAsyncManager.class})
|
||||||
@RunWith(PowerMockRunner.class)
|
@RunWith(PowerMockRunner.class)
|
||||||
|
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*", "javax.xml.transform.*" })
|
||||||
public class WebSecurityConfigurerAdapterTests {
|
public class WebSecurityConfigurerAdapterTests {
|
||||||
@Rule
|
@Rule
|
||||||
public final SpringTestRule spring = new SpringTestRule();
|
public final SpringTestRule spring = new SpringTestRule();
|
||||||
|
|||||||
+295
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-8
@@ -15,15 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.config.annotation.web.configurers;
|
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 java.lang.reflect.Method;
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
import javax.servlet.Filter;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
@@ -32,8 +24,10 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
|
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||||
import org.powermock.modules.junit4.PowerMockRunner;
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
import org.springframework.mock.web.MockFilterChain;
|
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.security.web.csrf.HttpSessionCsrfTokenRepository;
|
||||||
import org.springframework.util.ReflectionUtils;
|
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
|
* @author Rob Winch
|
||||||
*/
|
*/
|
||||||
@RunWith(PowerMockRunner.class)
|
@RunWith(PowerMockRunner.class)
|
||||||
@PrepareForTest({ ReflectionUtils.class, Method.class })
|
@PrepareForTest({ ReflectionUtils.class, Method.class })
|
||||||
|
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*" })
|
||||||
public class SessionManagementConfigurerServlet31Tests {
|
public class SessionManagementConfigurerServlet31Tests {
|
||||||
@Mock
|
@Mock
|
||||||
Method method;
|
Method method;
|
||||||
|
|||||||
+34
-1
@@ -21,6 +21,7 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.beans.PropertyAccessorFactory;
|
import org.springframework.beans.PropertyAccessorFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.http.MediaType;
|
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.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
@@ -156,6 +158,30 @@ public class OAuth2LoginConfigurerTests {
|
|||||||
.isInstanceOf(OAuth2UserAuthority.class).hasToString("ROLE_USER");
|
.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
|
@Test
|
||||||
public void oauth2LoginWhenAuthenticatedThenIgnored() throws Exception {
|
public void oauth2LoginWhenAuthenticatedThenIgnored() throws Exception {
|
||||||
// setup application context
|
// setup application context
|
||||||
@@ -467,7 +493,9 @@ public class OAuth2LoginConfigurerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
static class OAuth2LoginConfig extends CommonWebSecurityConfigurerAdapter {
|
static class OAuth2LoginConfig extends CommonWebSecurityConfigurerAdapter implements ApplicationListener<AuthenticationSuccessEvent> {
|
||||||
|
static List<AuthenticationSuccessEvent> EVENTS = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
@@ -476,6 +504,11 @@ public class OAuth2LoginConfigurerTests {
|
|||||||
new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION));
|
new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION));
|
||||||
super.configure(http);
|
super.configure(http);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(AuthenticationSuccessEvent event) {
|
||||||
|
EVENTS.add(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
|
|||||||
+10
-10
@@ -15,15 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.config.http;
|
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 java.lang.reflect.Method;
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
import javax.servlet.Filter;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
@@ -32,14 +24,14 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
|
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||||
import org.powermock.modules.junit4.PowerMockRunner;
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.mock.web.MockFilterChain;
|
import org.springframework.mock.web.MockFilterChain;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
|
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextImpl;
|
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.security.web.context.HttpSessionSecurityContextRepository;
|
||||||
import org.springframework.util.ReflectionUtils;
|
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
|
* @author Rob Winch
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@RunWith(PowerMockRunner.class)
|
@RunWith(PowerMockRunner.class)
|
||||||
@PrepareForTest({ ReflectionUtils.class, Method.class })
|
@PrepareForTest({ ReflectionUtils.class, Method.class })
|
||||||
|
@PowerMockIgnore({ "org.w3c.dom.*", "org.xml.sax.*", "org.apache.xerces.*", "javax.xml.parsers.*" })
|
||||||
public class SessionManagementConfigServlet31Tests {
|
public class SessionManagementConfigServlet31Tests {
|
||||||
private static final String XML_AUTHENTICATION_MANAGER = "<authentication-manager>"
|
private static final String XML_AUTHENTICATION_MANAGER = "<authentication-manager>"
|
||||||
+ " <authentication-provider>" + " <user-service>"
|
+ " <authentication-provider>" + " <user-service>"
|
||||||
|
|||||||
+3
-1
@@ -112,7 +112,9 @@ class MethodSecurityEvaluationContext extends StandardEvaluationContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < args.length; i++) {
|
for (int i = 0; i < args.length; i++) {
|
||||||
super.setVariable(paramNames[i], args[i]);
|
if (paramNames[i] != null) {
|
||||||
|
setVariable(paramNames[i], args[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-6
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.security.authentication;
|
package org.springframework.security.authentication;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -157,7 +156,9 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
|||||||
throws AuthenticationException {
|
throws AuthenticationException {
|
||||||
Class<? extends Authentication> toTest = authentication.getClass();
|
Class<? extends Authentication> toTest = authentication.getClass();
|
||||||
AuthenticationException lastException = null;
|
AuthenticationException lastException = null;
|
||||||
|
AuthenticationException parentException = null;
|
||||||
Authentication result = null;
|
Authentication result = null;
|
||||||
|
Authentication parentResult = null;
|
||||||
boolean debug = logger.isDebugEnabled();
|
boolean debug = logger.isDebugEnabled();
|
||||||
|
|
||||||
for (AuthenticationProvider provider : getProviders()) {
|
for (AuthenticationProvider provider : getProviders()) {
|
||||||
@@ -196,7 +197,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
|||||||
if (result == null && parent != null) {
|
if (result == null && parent != null) {
|
||||||
// Allow the parent to try.
|
// Allow the parent to try.
|
||||||
try {
|
try {
|
||||||
result = parent.authenticate(authentication);
|
result = parentResult = parent.authenticate(authentication);
|
||||||
}
|
}
|
||||||
catch (ProviderNotFoundException e) {
|
catch (ProviderNotFoundException e) {
|
||||||
// ignore as we will throw below if no other exception occurred prior to
|
// 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
|
// handled the request
|
||||||
}
|
}
|
||||||
catch (AuthenticationException e) {
|
catch (AuthenticationException e) {
|
||||||
lastException = e;
|
lastException = parentException = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +218,11 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
|||||||
((CredentialsContainer) result).eraseCredentials();
|
((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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +235,11 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
|||||||
"No AuthenticationProvider found for {0}"));
|
"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;
|
throw lastException;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-11
@@ -132,17 +132,7 @@ public class SessionRegistryImpl implements SessionRegistry,
|
|||||||
sessionIds.put(sessionId,
|
sessionIds.put(sessionId,
|
||||||
new SessionInformation(principal, sessionId, new Date()));
|
new SessionInformation(principal, sessionId, new Date()));
|
||||||
|
|
||||||
Set<String> sessionsUsedByPrincipal = principals.get(principal);
|
Set<String> sessionsUsedByPrincipal = principals.computeIfAbsent(principal, key -> new CopyOnWriteArraySet<>());
|
||||||
|
|
||||||
if (sessionsUsedByPrincipal == null) {
|
|
||||||
sessionsUsedByPrincipal = new CopyOnWriteArraySet<>();
|
|
||||||
Set<String> prevSessionsUsedByPrincipal = principals.putIfAbsent(principal,
|
|
||||||
sessionsUsedByPrincipal);
|
|
||||||
if (prevSessionsUsedByPrincipal != null) {
|
|
||||||
sessionsUsedByPrincipal = prevSessionsUsedByPrincipal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionsUsedByPrincipal.add(sessionId);
|
sessionsUsedByPrincipal.add(sessionId);
|
||||||
|
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
|
|||||||
+19
-4
@@ -57,6 +57,7 @@ import java.util.Set;
|
|||||||
* mapper.registerModule(new CoreJackson2Module());
|
* mapper.registerModule(new CoreJackson2Module());
|
||||||
* mapper.registerModule(new CasJackson2Module());
|
* mapper.registerModule(new CasJackson2Module());
|
||||||
* mapper.registerModule(new WebJackson2Module());
|
* mapper.registerModule(new WebJackson2Module());
|
||||||
|
* mapper.registerModule(new WebServletJackson2Module());
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @author Jitendra Singh.
|
* @author Jitendra Singh.
|
||||||
@@ -70,6 +71,8 @@ public final class SecurityJackson2Modules {
|
|||||||
"org.springframework.security.cas.jackson2.CasJackson2Module",
|
"org.springframework.security.cas.jackson2.CasJackson2Module",
|
||||||
"org.springframework.security.web.jackson2.WebJackson2Module"
|
"org.springframework.security.web.jackson2.WebJackson2Module"
|
||||||
);
|
);
|
||||||
|
private static final String webServletJackson2ModuleClass =
|
||||||
|
"org.springframework.security.web.jackson2.WebServletJackson2Module";
|
||||||
|
|
||||||
private SecurityJackson2Modules() {
|
private SecurityJackson2Modules() {
|
||||||
}
|
}
|
||||||
@@ -109,14 +112,26 @@ public final class SecurityJackson2Modules {
|
|||||||
public static List<Module> getModules(ClassLoader loader) {
|
public static List<Module> getModules(ClassLoader loader) {
|
||||||
List<Module> modules = new ArrayList<>();
|
List<Module> modules = new ArrayList<>();
|
||||||
for (String className : securityJackson2ModuleClasses) {
|
for (String className : securityJackson2ModuleClasses) {
|
||||||
Module module = loadAndGetInstance(className, loader);
|
addToModulesList(loader, modules, className);
|
||||||
if (module != null) {
|
}
|
||||||
modules.add(module);
|
if (ClassUtils.isPresent("javax.servlet.http.Cookie", loader)) {
|
||||||
}
|
addToModulesList(loader, modules, webServletJackson2ModuleClass);
|
||||||
}
|
}
|
||||||
return modules;
|
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.
|
* Creates a TypeResolverBuilder that performs whitelisting.
|
||||||
* @return a TypeResolverBuilder that performs whitelisting.
|
* @return a TypeResolverBuilder that performs whitelisting.
|
||||||
|
|||||||
+3
-2
@@ -41,6 +41,7 @@ import org.springframework.security.core.GrantedAuthority;
|
|||||||
*
|
*
|
||||||
* @author Jitendra Singh
|
* @author Jitendra Singh
|
||||||
* @author Greg Turnquist
|
* @author Greg Turnquist
|
||||||
|
* @author Onur Kagan Ozcan
|
||||||
* @see UsernamePasswordAuthenticationTokenMixin
|
* @see UsernamePasswordAuthenticationTokenMixin
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
@@ -69,7 +70,7 @@ class UsernamePasswordAuthenticationTokenDeserializer extends JsonDeserializer<U
|
|||||||
}
|
}
|
||||||
JsonNode credentialsNode = readJsonNode(jsonNode, "credentials");
|
JsonNode credentialsNode = readJsonNode(jsonNode, "credentials");
|
||||||
Object credentials;
|
Object credentials;
|
||||||
if (credentialsNode.isNull()) {
|
if (credentialsNode.isNull() || credentialsNode.isMissingNode()) {
|
||||||
credentials = null;
|
credentials = null;
|
||||||
} else {
|
} else {
|
||||||
credentials = credentialsNode.asText();
|
credentials = credentialsNode.asText();
|
||||||
@@ -83,7 +84,7 @@ class UsernamePasswordAuthenticationTokenDeserializer extends JsonDeserializer<U
|
|||||||
token = new UsernamePasswordAuthenticationToken(principal, credentials);
|
token = new UsernamePasswordAuthenticationToken(principal, credentials);
|
||||||
}
|
}
|
||||||
JsonNode detailsNode = readJsonNode(jsonNode, "details");
|
JsonNode detailsNode = readJsonNode(jsonNode, "details");
|
||||||
if (detailsNode.isNull()) {
|
if (detailsNode.isNull() || detailsNode.isMissingNode()) {
|
||||||
token.setDetails(null);
|
token.setDetails(null);
|
||||||
} else {
|
} else {
|
||||||
token.setDetails(detailsNode);
|
token.setDetails(detailsNode);
|
||||||
|
|||||||
+1
-1
@@ -143,7 +143,7 @@ public class InMemoryUserDetailsManager implements UserDetailsManager,
|
|||||||
@Override
|
@Override
|
||||||
public UserDetails updatePassword(UserDetails user, String newPassword) {
|
public UserDetails updatePassword(UserDetails user, String newPassword) {
|
||||||
String username = user.getUsername();
|
String username = user.getUsername();
|
||||||
MutableUserDetails mutableUser = this.users.get(username);
|
MutableUserDetails mutableUser = this.users.get(username.toLowerCase());
|
||||||
mutableUser.setPassword(newPassword);
|
mutableUser.setPassword(newPassword);
|
||||||
return mutableUser;
|
return mutableUser;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa
|
|||||||
// GroupManager SQL
|
// GroupManager SQL
|
||||||
public static final String DEF_FIND_GROUPS_SQL = "select group_name from groups";
|
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 "
|
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_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_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 (?,?)";
|
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
|
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
|
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.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
|
||||||
AbstractUserDetailsAuthenticationProvider.disabled=\u7528\u6237\u5DF2\u5931\u6548
|
AbstractUserDetailsAuthenticationProvider.disabled=\u7528\u6237\u5DF2\u5931\u6548
|
||||||
AbstractUserDetailsAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
|
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
|
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})
|
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
|
AnonymousAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684AnonymousAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
|
||||||
BindAuthenticator.badCredentials=\u574F\u7684\u51ED\u8BC1
|
BindAuthenticator.badCredentials=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
|
||||||
BindAuthenticator.emptyPassword=\u574F\u7684\u51ED\u8BC1
|
BindAuthenticator.emptyPassword=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
|
||||||
CasAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684CasAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
|
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
|
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
|
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
|
DigestAuthenticationFilter.usernameNotFound=\u7528\u6237\u540D{0}\u672A\u627E\u5230
|
||||||
JdbcDaoImpl.noAuthority=\u6CA1\u6709\u4E3A\u7528\u6237{0}\u6307\u5B9A\u89D2\u8272
|
JdbcDaoImpl.noAuthority=\u6CA1\u6709\u4E3A\u7528\u6237{0}\u6307\u5B9A\u89D2\u8272
|
||||||
JdbcDaoImpl.notFound=\u672A\u627E\u5230\u7528\u6237{0}
|
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.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
|
||||||
LdapAuthenticationProvider.disabled=\u7528\u6237\u5DF2\u5931\u6548
|
LdapAuthenticationProvider.disabled=\u7528\u6237\u5DF2\u5931\u6548
|
||||||
LdapAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
|
LdapAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
|
||||||
LdapAuthenticationProvider.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
|
LdapAuthenticationProvider.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
|
||||||
LdapAuthenticationProvider.emptyUsername=\u7528\u6237\u540D\u4E0D\u5141\u8BB8\u4E3A\u7A7A
|
LdapAuthenticationProvider.emptyUsername=\u7528\u6237\u540D\u4E0D\u5141\u8BB8\u4E3A\u7A7A
|
||||||
LdapAuthenticationProvider.onlySupports=\u4EC5\u4EC5\u652F\u6301UsernamePasswordAuthenticationToken
|
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.
|
#PersistentTokenBasedRememberMeServices.cookieStolen=Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.
|
||||||
ProviderManager.providerNotFound=\u672A\u67E5\u627E\u5230\u9488\u5BF9{0}\u7684AuthenticationProvider
|
ProviderManager.providerNotFound=\u672A\u67E5\u627E\u5230\u9488\u5BF9{0}\u7684AuthenticationProvider
|
||||||
RememberMeAuthenticationProvider.incorrectKey=\u5C55\u793ARememberMeAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
|
RememberMeAuthenticationProvider.incorrectKey=\u5C55\u793ARememberMeAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
|
||||||
|
|||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2016 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.access.expression.method;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import org.springframework.core.ParameterNameDiscoverer;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shabarijonnalagadda
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class MethodSecurityEvaluationContextTests {
|
||||||
|
@Mock
|
||||||
|
private ParameterNameDiscoverer paramNameDiscoverer;
|
||||||
|
@Mock
|
||||||
|
private Authentication authentication;
|
||||||
|
@Mock
|
||||||
|
private MethodInvocation methodInvocation;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void lookupVariableWhenParameterNameNullThenNotSet() {
|
||||||
|
Class<String> type = String.class;
|
||||||
|
Method method = ReflectionUtils.findMethod(String.class, "contains", CharSequence.class);
|
||||||
|
doReturn(new String[] {null}).when(paramNameDiscoverer).getParameterNames(method);
|
||||||
|
doReturn(new Object[]{null}).when(methodInvocation).getArguments();
|
||||||
|
doReturn(type).when(methodInvocation).getThis();
|
||||||
|
doReturn(method).when(methodInvocation).getMethod();
|
||||||
|
NotNullVariableMethodSecurityEvaluationContext context= new NotNullVariableMethodSecurityEvaluationContext(authentication, methodInvocation, paramNameDiscoverer);
|
||||||
|
context.lookupVariable("testVariable");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NotNullVariableMethodSecurityEvaluationContext
|
||||||
|
extends MethodSecurityEvaluationContext {
|
||||||
|
|
||||||
|
public NotNullVariableMethodSecurityEvaluationContext(Authentication auth, MethodInvocation mi,
|
||||||
|
ParameterNameDiscoverer parameterNameDiscoverer) {
|
||||||
|
super(auth, mi, parameterNameDiscoverer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVariable(String name, @Nullable Object value) {
|
||||||
|
if ( name == null ) {
|
||||||
|
throw new IllegalArgumentException("name should not be null");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
super.setVariable(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
-8
@@ -16,18 +16,20 @@
|
|||||||
|
|
||||||
package org.springframework.security.authentication;
|
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.junit.Test;
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
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}.
|
* Tests {@link ProviderManager}.
|
||||||
*
|
*
|
||||||
@@ -257,7 +259,6 @@ public class ProviderManagerTests {
|
|||||||
catch (BadCredentialsException e) {
|
catch (BadCredentialsException e) {
|
||||||
assertThat(e).isSameAs(expected);
|
assertThat(e).isSameAs(expected);
|
||||||
}
|
}
|
||||||
verify(publisher).publishAuthenticationFailure(expected, authReq);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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(
|
private AuthenticationProvider createProviderWhichThrows(
|
||||||
final AuthenticationException e) {
|
final AuthenticationException e) {
|
||||||
AuthenticationProvider provider = mock(AuthenticationProvider.class);
|
AuthenticationProvider provider = mock(AuthenticationProvider.class);
|
||||||
|
|||||||
+20
-1
@@ -29,11 +29,16 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
|||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.userdetails.User;
|
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 Jitendra Singh
|
||||||
* @author Greg Turnquist
|
* @author Greg Turnquist
|
||||||
|
* @author Onur Kagan Ozcan
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
public class UsernamePasswordAuthenticationTokenMixinTests extends AbstractMixinTests {
|
public class UsernamePasswordAuthenticationTokenMixinTests extends AbstractMixinTests {
|
||||||
@@ -163,6 +168,20 @@ public class UsernamePasswordAuthenticationTokenMixinTests extends AbstractMixin
|
|||||||
assertThat(deserialized).isEqualTo(original);
|
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() {
|
private UsernamePasswordAuthenticationToken createToken() {
|
||||||
User user = createDefaultUser();
|
User user = createDefaultUser();
|
||||||
|
|||||||
+12
@@ -18,6 +18,7 @@ package org.springframework.security.provisioning;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.security.core.userdetails.PasswordEncodedUser;
|
import org.springframework.security.core.userdetails.PasswordEncodedUser;
|
||||||
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.*;
|
import static org.assertj.core.api.Assertions.*;
|
||||||
@@ -37,4 +38,15 @@ public class InMemoryUserDetailsManagerTests {
|
|||||||
this.manager.updatePassword(this.user, newPassword);
|
this.manager.updatePassword(this.user, newPassword);
|
||||||
assertThat(this.manager.loadUserByUsername(this.user.getUsername()).getPassword()).isEqualTo(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>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.thymeleaf.extras</groupId>
|
<groupId>org.thymeleaf.extras</groupId>
|
||||||
<artifactId>thymeleaf-extras-springsecurity4</artifactId> <1>
|
<artifactId>thymeleaf-extras-springsecurity5</artifactId> <1>
|
||||||
<version>2.1.2.RELEASE</version>
|
<version>2.1.2.RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ Now that we have authenticated, let's update the application to display the user
|
|||||||
[source,html]
|
[source,html]
|
||||||
----
|
----
|
||||||
<!DOCTYPE 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>
|
<head>
|
||||||
<title>Hello Spring Security</title>
|
<title>Hello Spring Security</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ If you wish to override the Spring Security version, you may do so by providing
|
|||||||
----
|
----
|
||||||
<properties>
|
<properties>
|
||||||
<!-- ... -->
|
<!-- ... -->
|
||||||
<spring-security.version>{spring-security-version}</spring.security.version>
|
<spring-security.version>{spring-security-version}</spring-security.version>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,30 @@
|
|||||||
[[webflux-oauth2-resource-server]]
|
[[webflux-oauth2-resource-server]]
|
||||||
= 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]
|
[NOTE]
|
||||||
====
|
====
|
||||||
A complete working example can be found in {gh-samples-url}/boot/oauth2resourceserver-webflux[*OAuth 2.0 Resource Server WebFlux sample*].
|
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`.
|
== Dependencies
|
||||||
In a Spring Boot application this can be done using:
|
|
||||||
|
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]
|
[source,yml]
|
||||||
----
|
----
|
||||||
@@ -19,15 +33,107 @@ spring:
|
|||||||
oauth2:
|
oauth2:
|
||||||
resourceserver:
|
resourceserver:
|
||||||
jwt:
|
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.
|
Where `https://idp.example.com` is the value contained in the `iss` claim for JWT tokens that the authorization server will issue.
|
||||||
The above is all that is necessary to get a minimal Resource Server configured.
|
Resource Server will use this property to further self-configure, discover the authorization server's public keys, and subsequently validate incoming JWTs.
|
||||||
When new keys are made available, Spring Security will automatically rotate the keys used to validate the JWT tokens.
|
|
||||||
|
|
||||||
By default each scope is mapped to an authority with the prefix `SCOPE_`.
|
[NOTE]
|
||||||
For example, the following requires the scope of `message:read` for any URL that starts with `/messages/`.
|
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]
|
||||||
|
----
|
||||||
|
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]
|
[source,java]
|
||||||
----
|
----
|
||||||
@@ -44,4 +150,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;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
Spring Security has added Jackson Support for persisting Spring Security related classes.
|
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).
|
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]
|
[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`.
|
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".
|
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.
|
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 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.
|
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
|
=== Dependencies
|
||||||
|
|
||||||
Most Resource Server support is collected into `spring-security-oauth2-resource-server`.
|
Most Resource Server support is collected into `spring-security-oauth2-resource-server`.
|
||||||
@@ -417,25 +422,27 @@ security:
|
|||||||
oauth2:
|
oauth2:
|
||||||
resourceserver:
|
resourceserver:
|
||||||
jwt:
|
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.
|
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 and subsequently validate incoming JWTs.
|
Resource Server will use this property to further self-configure, discover the authorization server's public keys, and subsequently validate incoming JWTs.
|
||||||
|
|
||||||
[NOTE]
|
[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.
|
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
|
==== Startup Expectations
|
||||||
|
|
||||||
When this property and these dependencies are used, Resource Server will automatically configure itself to validate JWT-encoded Bearer Tokens.
|
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:
|
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
|
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.
|
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
|
==== 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
|
```http
|
||||||
GET / HTTP/1.1
|
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.
|
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.
|
Given a well-formed JWT token, Resource Server will
|
||||||
It will also validate the signature against a public key obtained from the `jwks_url` endpoint and matched against the JWTs header.
|
|
||||||
|
|
||||||
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:
|
From here, consider jumping to:
|
||||||
|
|
||||||
@@ -474,7 +487,7 @@ security:
|
|||||||
oauth2:
|
oauth2:
|
||||||
resourceserver:
|
resourceserver:
|
||||||
jwt:
|
jwt:
|
||||||
jwk-set-uri: https://the.issuer.location/.well-known/jwks.json
|
jwk-set-uri: https://idp.example.com/.well-known/jwks.json
|
||||||
```
|
```
|
||||||
|
|
||||||
[NOTE]
|
[NOTE]
|
||||||
@@ -514,7 +527,7 @@ public class MyCustomSecurityConfiguration extends WebSecurityConfigurerAdapter
|
|||||||
protected void configure(HttpSecurity http) {
|
protected void configure(HttpSecurity http) {
|
||||||
http
|
http
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.mvcMatchers("/admin/**").hasAuthority("SCOPE_admin")
|
.mvcMatchers("/messages/**").hasAuthority("SCOPE_message:read")
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
.and()
|
.and()
|
||||||
.oauth2ResourceServer()
|
.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.
|
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`:
|
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()
|
.and()
|
||||||
.oauth2ResourceServer()
|
.oauth2ResourceServer()
|
||||||
.jwt()
|
.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]]
|
[[oauth2resourceserver-decoder-bean]]
|
||||||
==== Exposing a `JwtDecoder` `@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"}`
|
`{ ..., "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:
|
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
|
==== Extracting Authorities Manually
|
||||||
|
|
||||||
However, there are a number of circumstances where this default is insufficient.
|
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.
|
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()`:
|
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:
|
By default, `MappedJwtClaimSetConverter` will attempt to coerce claims into the following types:
|
||||||
|
|
||||||
|
|
||||||
|============
|
|============
|
||||||
| Claim | Java Type
|
| Claim | Java Type
|
||||||
| `aud` | `Collection<String>`
|
| `aud` | `Collection<String>`
|
||||||
| `exp` | `Instant`
|
| `exp` | `Instant`
|
||||||
| `iat` | `Instant`
|
| `iat` | `Instant`
|
||||||
| `iss` | `URL`
|
| `iss` | `String`
|
||||||
| `jti` | `String`
|
| `jti` | `String`
|
||||||
| `nbf` | `Instant`
|
| `nbf` | `Instant`
|
||||||
| `sub` | `String`
|
| `sub` | `String`
|
||||||
|
|||||||
+3
-3
@@ -1,3 +1,3 @@
|
|||||||
gaeVersion=1.9.66
|
gaeVersion=1.9.68
|
||||||
springBootVersion=2.1.0.M4
|
springBootVersion=2.1.1.RELEASE
|
||||||
version=5.1.2.BUILD-SNAPSHOT
|
version=5.1.3.RELEASE
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
if (!project.hasProperty('reactorVersion')) {
|
if (!project.hasProperty('reactorVersion')) {
|
||||||
ext.reactorVersion = 'Californium-SR1'
|
ext.reactorVersion = 'Californium-SR4'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project.hasProperty('springVersion')) {
|
if (!project.hasProperty('springVersion')) {
|
||||||
ext.springVersion = '5.1.1.RELEASE'
|
ext.springVersion = '5.1.4.RELEASE'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project.hasProperty('springDataVersion')) {
|
if (!project.hasProperty('springDataVersion')) {
|
||||||
ext.springDataVersion = 'Lovelace-SR1'
|
ext.springDataVersion = 'Lovelace-SR4'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyManagement {
|
dependencyManagement {
|
||||||
@@ -17,17 +17,17 @@ dependencyManagement {
|
|||||||
mavenBom "org.springframework.data:spring-data-releasetrain:${springDataVersion}"
|
mavenBom "org.springframework.data:spring-data-releasetrain:${springDataVersion}"
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
dependency 'cglib:cglib-nodep:3.2.8'
|
dependency 'cglib:cglib-nodep:3.2.10'
|
||||||
dependency 'com.squareup.okhttp3:mockwebserver:3.10.0'
|
dependency 'com.squareup.okhttp3:mockwebserver:3.12.1'
|
||||||
dependency 'opensymphony:sitemesh:2.4.2'
|
dependency 'opensymphony:sitemesh:2.4.2'
|
||||||
dependency 'org.gebish:geb-spock:0.10.0'
|
dependency 'org.gebish:geb-spock:0.10.0'
|
||||||
dependency 'org.jasig.cas:cas-server-webapp:4.2.7'
|
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-mockito2:2.0.0'
|
||||||
dependency 'org.powermock:powermock-api-support:2.0.0-beta.5'
|
dependency 'org.powermock:powermock-api-support:2.0.0'
|
||||||
dependency 'org.powermock:powermock-core:2.0.0-beta.5'
|
dependency 'org.powermock:powermock-core:2.0.0'
|
||||||
dependency 'org.powermock:powermock-module-junit4-common:2.0.0-beta.5'
|
dependency 'org.powermock:powermock-module-junit4-common:2.0.0'
|
||||||
dependency 'org.powermock:powermock-module-junit4:2.0.0-beta.5'
|
dependency 'org.powermock:powermock-module-junit4:2.0.0'
|
||||||
dependency 'org.powermock:powermock-reflect:2.0.0-beta.5'
|
dependency 'org.powermock:powermock-reflect:2.0.0'
|
||||||
dependency 'org.python:jython:2.5.0'
|
dependency 'org.python:jython:2.5.0'
|
||||||
dependency 'org.spockframework:spock-core:1.0-groovy-2.4'
|
dependency 'org.spockframework:spock-core:1.0-groovy-2.4'
|
||||||
dependency 'org.spockframework:spock-spring: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 'asm:asm:3.1'
|
||||||
dependency 'ch.qos.logback:logback-classic:1.2.3'
|
dependency 'ch.qos.logback:logback-classic:1.2.3'
|
||||||
dependency 'ch.qos.logback:logback-core: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-annotations:2.9.8'
|
||||||
dependency 'com.fasterxml.jackson.core:jackson-core:2.9.7'
|
dependency 'com.fasterxml.jackson.core:jackson-core:2.9.8'
|
||||||
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.7'
|
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
|
||||||
dependency 'com.fasterxml:classmate:1.3.4'
|
dependency 'com.fasterxml:classmate:1.3.4'
|
||||||
dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1'
|
dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1'
|
||||||
dependency 'com.google.appengine:appengine-api-1.0-sdk:1.9.64'
|
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:lang-tag:1.4.3'
|
||||||
dependency 'com.nimbusds:nimbus-jose-jwt:6.0.2'
|
dependency 'com.nimbusds:nimbus-jose-jwt:6.0.2'
|
||||||
dependency 'com.nimbusds:oauth2-oidc-sdk:6.0'
|
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.squareup.okio:okio:1.13.0'
|
||||||
dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1'
|
dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1'
|
||||||
dependency 'com.sun.xml.bind:jaxb-impl: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 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1'
|
||||||
dependency 'commons-cli:commons-cli:1.4'
|
dependency 'commons-cli:commons-cli:1.4'
|
||||||
dependency 'commons-codec:commons-codec:1.11'
|
dependency 'commons-codec:commons-codec:1.11'
|
||||||
@@ -81,12 +81,12 @@ dependencyManagement {
|
|||||||
dependency 'javax.xml.bind:jaxb-api:2.4.0-b180830.0359'
|
dependency 'javax.xml.bind:jaxb-api:2.4.0-b180830.0359'
|
||||||
dependency 'junit:junit:4.12'
|
dependency 'junit:junit:4.12'
|
||||||
dependency 'ldapsdk:ldapsdk:4.1'
|
dependency 'ldapsdk:ldapsdk:4.1'
|
||||||
dependency 'net.bytebuddy:byte-buddy-agent:1.7.9'
|
dependency 'net.bytebuddy:byte-buddy-agent:1.8.3'
|
||||||
dependency 'net.bytebuddy:byte-buddy:1.7.9'
|
dependency 'net.bytebuddy:byte-buddy:1.8.3'
|
||||||
dependency 'net.jcip:jcip-annotations:1.0'
|
dependency 'net.jcip:jcip-annotations:1.0'
|
||||||
dependency 'net.minidev:accessors-smart:1.2'
|
dependency 'net.minidev:accessors-smart:1.2'
|
||||||
dependency 'net.minidev:json-smart:2.3'
|
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:htmlunit:2.33'
|
||||||
dependency 'net.sourceforge.htmlunit:neko-htmlunit:2.32'
|
dependency 'net.sourceforge.htmlunit:neko-htmlunit:2.32'
|
||||||
dependency 'net.sourceforge.nekohtml:nekohtml:1.9.22'
|
dependency 'net.sourceforge.nekohtml:nekohtml:1.9.22'
|
||||||
@@ -140,9 +140,9 @@ dependencyManagement {
|
|||||||
dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:8.0.44'
|
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.embed:tomcat-embed-websocket:8.5.23'
|
||||||
dependency 'org.apache.tomcat:tomcat-annotations-api:8.5.23'
|
dependency 'org.apache.tomcat:tomcat-annotations-api:8.5.23'
|
||||||
dependency 'org.aspectj:aspectjrt:1.9.1'
|
dependency 'org.aspectj:aspectjrt:1.9.2'
|
||||||
dependency 'org.aspectj:aspectjtools:1.9.1'
|
dependency 'org.aspectj:aspectjtools:1.9.2'
|
||||||
dependency 'org.aspectj:aspectjweaver:1.9.1'
|
dependency 'org.aspectj:aspectjweaver:1.9.2'
|
||||||
dependency 'org.assertj:assertj-core:3.11.1'
|
dependency 'org.assertj:assertj-core:3.11.1'
|
||||||
dependency 'org.attoparser:attoparser:2.0.4.RELEASE'
|
dependency 'org.attoparser:attoparser:2.0.4.RELEASE'
|
||||||
dependency 'org.bouncycastle:bcpkix-jdk15on:1.60'
|
dependency 'org.bouncycastle:bcpkix-jdk15on:1.60'
|
||||||
@@ -179,10 +179,10 @@ dependencyManagement {
|
|||||||
dependency 'org.jboss.logging:jboss-logging:3.3.1.Final'
|
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.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.0.1.Final'
|
||||||
dependency 'org.jboss:jandex:2.0.3.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.objenesis:objenesis:2.6'
|
||||||
dependency 'org.openid4java:openid4java-nodeps:0.9.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.reactivestreams:reactive-streams:1.0.1'
|
||||||
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.33.0'
|
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.33.0'
|
||||||
dependency 'org.seleniumhq.selenium:selenium-java:3.14.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.slf4j:slf4j-nop:1.7.25'
|
||||||
dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111'
|
dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111'
|
||||||
dependency 'org.springframework.ldap:spring-ldap-core:2.3.2.RELEASE'
|
dependency 'org.springframework.ldap:spring-ldap-core:2.3.2.RELEASE'
|
||||||
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.10.RELEASE'
|
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE'
|
||||||
dependency 'org.thymeleaf:thymeleaf:3.0.9.RELEASE'
|
|
||||||
dependency 'org.unbescape:unbescape:1.1.5.RELEASE'
|
dependency 'org.unbescape:unbescape:1.1.5.RELEASE'
|
||||||
dependency 'org.w3c.css:sac:1.3'
|
dependency 'org.w3c.css:sac:1.3'
|
||||||
dependency 'xalan:serializer:2.7.2'
|
dependency 'xalan:serializer:2.7.2'
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
+12
-1
@@ -25,6 +25,7 @@ import org.springframework.util.CollectionUtils;
|
|||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.reactive.function.BodyInserters;
|
import org.springframework.web.reactive.function.BodyInserters;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -64,7 +65,17 @@ public class WebClientReactiveClientCredentialsTokenResponseClient implements Re
|
|||||||
.headers(headers(clientRegistration))
|
.headers(headers(clientRegistration))
|
||||||
.body(body)
|
.body(body)
|
||||||
.exchange()
|
.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 -> {
|
.map(response -> {
|
||||||
if (response.getAccessToken().getScopes().isEmpty()) {
|
if (response.getAccessToken().getScopes().isEmpty()) {
|
||||||
response = OAuth2AccessTokenResponse.withResponse(response)
|
response = OAuth2AccessTokenResponse.withResponse(response)
|
||||||
|
|||||||
+12
-10
@@ -18,16 +18,13 @@ package org.springframework.security.oauth2.client.registration;
|
|||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collector;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static java.util.stream.Collectors.collectingAndThen;
|
|
||||||
import static java.util.stream.Collectors.toConcurrentMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link ClientRegistrationRepository} that stores {@link ClientRegistration}(s) in-memory.
|
* A {@link ClientRegistrationRepository} that stores {@link ClientRegistration}(s) in-memory.
|
||||||
@@ -39,6 +36,7 @@ import static java.util.stream.Collectors.toConcurrentMap;
|
|||||||
* @see ClientRegistration
|
* @see ClientRegistration
|
||||||
*/
|
*/
|
||||||
public final class InMemoryClientRegistrationRepository implements ClientRegistrationRepository, Iterable<ClientRegistration> {
|
public final class InMemoryClientRegistrationRepository implements ClientRegistrationRepository, Iterable<ClientRegistration> {
|
||||||
|
|
||||||
private final Map<String, ClientRegistration> registrations;
|
private final Map<String, ClientRegistration> registrations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,7 +45,8 @@ public final class InMemoryClientRegistrationRepository implements ClientRegistr
|
|||||||
* @param registrations the client registration(s)
|
* @param registrations the client registration(s)
|
||||||
*/
|
*/
|
||||||
public InMemoryClientRegistrationRepository(ClientRegistration... registrations) {
|
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) {
|
public InMemoryClientRegistrationRepository(List<ClientRegistration> registrations) {
|
||||||
Assert.notEmpty(registrations, "registrations cannot be empty");
|
Assert.notEmpty(registrations, "registrations cannot be empty");
|
||||||
Collector<ClientRegistration, ?, ConcurrentMap<String, ClientRegistration>> collector =
|
this.registrations = createClientRegistrationIdToClientRegistration(registrations);
|
||||||
toConcurrentMap(ClientRegistration::getRegistrationId, Function.identity());
|
}
|
||||||
this.registrations = registrations.stream()
|
|
||||||
.collect(collectingAndThen(collector, Collections::unmodifiableMap));
|
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
|
@Override
|
||||||
|
|||||||
+5
-19
@@ -17,12 +17,6 @@ package org.springframework.security.oauth2.client.registration;
|
|||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
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;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
@@ -37,7 +31,7 @@ import reactor.core.publisher.Mono;
|
|||||||
public final class InMemoryReactiveClientRegistrationRepository
|
public final class InMemoryReactiveClientRegistrationRepository
|
||||||
implements ReactiveClientRegistrationRepository, Iterable<ClientRegistration> {
|
implements ReactiveClientRegistrationRepository, Iterable<ClientRegistration> {
|
||||||
|
|
||||||
private final Map<String, ClientRegistration> clientIdToClientRegistration;
|
private final InMemoryClientRegistrationRepository delegate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an {@code InMemoryReactiveClientRegistrationRepository} using the provided parameters.
|
* Constructs an {@code InMemoryReactiveClientRegistrationRepository} using the provided parameters.
|
||||||
@@ -45,12 +39,7 @@ public final class InMemoryReactiveClientRegistrationRepository
|
|||||||
* @param registrations the client registration(s)
|
* @param registrations the client registration(s)
|
||||||
*/
|
*/
|
||||||
public InMemoryReactiveClientRegistrationRepository(ClientRegistration... registrations) {
|
public InMemoryReactiveClientRegistrationRepository(ClientRegistration... registrations) {
|
||||||
Assert.notEmpty(registrations, "registrations cannot be empty");
|
this.delegate = new InMemoryClientRegistrationRepository(registrations);
|
||||||
this.clientIdToClientRegistration = new ConcurrentReferenceHashMap<>();
|
|
||||||
for (ClientRegistration registration : registrations) {
|
|
||||||
Assert.notNull(registration, "registrations cannot contain null values");
|
|
||||||
this.clientIdToClientRegistration.put(registration.getRegistrationId(), registration);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,15 +48,12 @@ public final class InMemoryReactiveClientRegistrationRepository
|
|||||||
* @param registrations the client registration(s)
|
* @param registrations the client registration(s)
|
||||||
*/
|
*/
|
||||||
public InMemoryReactiveClientRegistrationRepository(List<ClientRegistration> registrations) {
|
public InMemoryReactiveClientRegistrationRepository(List<ClientRegistration> registrations) {
|
||||||
Assert.notEmpty(registrations, "registrations cannot be null or empty");
|
this.delegate = new InMemoryClientRegistrationRepository(registrations);
|
||||||
this.clientIdToClientRegistration = registrations.stream()
|
|
||||||
.collect(Collectors.toConcurrentMap(ClientRegistration::getRegistrationId, Function.identity()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ClientRegistration> findByRegistrationId(String registrationId) {
|
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
|
@Override
|
||||||
public Iterator<ClientRegistration> iterator() {
|
public Iterator<ClientRegistration> iterator() {
|
||||||
return this.clientIdToClientRegistration.values().iterator();
|
return delegate.iterator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -60,7 +60,7 @@ public class OAuth2UserRequestEntityConverter implements Converter<OAuth2UserReq
|
|||||||
httpMethod = HttpMethod.POST;
|
httpMethod = HttpMethod.POST;
|
||||||
}
|
}
|
||||||
HttpHeaders headers = new HttpHeaders();
|
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())
|
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())
|
||||||
.build()
|
.build()
|
||||||
.toUri();
|
.toUri();
|
||||||
|
|||||||
+18
@@ -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.client.registration.TestClientRegistrations;
|
||||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.*;
|
import static org.assertj.core.api.Assertions.*;
|
||||||
|
|
||||||
@@ -116,6 +117,23 @@ public class WebClientReactiveClientCredentialsTokenResponseClientTests {
|
|||||||
assertThat(response.getAccessToken().getScopes()).isEqualTo(registration.getScopes());
|
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) {
|
private void enqueueJson(String body) {
|
||||||
MockResponse response = new MockResponse()
|
MockResponse response = new MockResponse()
|
||||||
|
|||||||
+3
-3
@@ -316,7 +316,7 @@ public class OidcUserServiceTests {
|
|||||||
|
|
||||||
this.userService.loadUser(new OidcUserRequest(this.clientRegistration, this.accessToken, this.idToken));
|
this.userService.loadUser(new OidcUserRequest(this.clientRegistration, this.accessToken, this.idToken));
|
||||||
assertThat(this.server.takeRequest(1, TimeUnit.SECONDS).getHeader(HttpHeaders.ACCEPT))
|
assertThat(this.server.takeRequest(1, TimeUnit.SECONDS).getHeader(HttpHeaders.ACCEPT))
|
||||||
.isEqualTo(MediaType.APPLICATION_JSON_UTF8_VALUE);
|
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// gh-5500
|
// gh-5500
|
||||||
@@ -341,7 +341,7 @@ public class OidcUserServiceTests {
|
|||||||
this.userService.loadUser(new OidcUserRequest(this.clientRegistration, this.accessToken, this.idToken));
|
this.userService.loadUser(new OidcUserRequest(this.clientRegistration, this.accessToken, this.idToken));
|
||||||
RecordedRequest request = this.server.takeRequest();
|
RecordedRequest request = this.server.takeRequest();
|
||||||
assertThat(request.getMethod()).isEqualTo(HttpMethod.GET.name());
|
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());
|
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));
|
this.userService.loadUser(new OidcUserRequest(this.clientRegistration, this.accessToken, this.idToken));
|
||||||
RecordedRequest request = this.server.takeRequest();
|
RecordedRequest request = this.server.takeRequest();
|
||||||
assertThat(request.getMethod()).isEqualTo(HttpMethod.POST.name());
|
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.getHeader(HttpHeaders.CONTENT_TYPE)).contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||||
assertThat(request.getBody().readUtf8()).isEqualTo("access_token=" + this.accessToken.getTokenValue());
|
assertThat(request.getBody().readUtf8()).isEqualTo("access_token=" + this.accessToken.getTokenValue());
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -35,6 +35,12 @@ public class InMemoryClientRegistrationRepositoryTests {
|
|||||||
|
|
||||||
private InMemoryClientRegistrationRepository clients = new InMemoryClientRegistrationRepository(this.registration);
|
private InMemoryClientRegistrationRepository clients = new InMemoryClientRegistrationRepository(this.registration);
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void constructorVarArgsListClientRegistrationWhenNullThenIllegalArgumentException() {
|
||||||
|
ClientRegistration nullRegistration = null;
|
||||||
|
new InMemoryClientRegistrationRepository(nullRegistration);
|
||||||
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void constructorListClientRegistrationWhenNullThenIllegalArgumentException() {
|
public void constructorListClientRegistrationWhenNullThenIllegalArgumentException() {
|
||||||
List<ClientRegistration> registrations = null;
|
List<ClientRegistration> registrations = null;
|
||||||
|
|||||||
+9
-2
@@ -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.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@@ -61,10 +62,16 @@ public class InMemoryReactiveClientRegistrationRepositoryTests {
|
|||||||
.isInstanceOf(IllegalArgumentException.class);
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenClientRegistrationListHasNullThenIllegalArgumentException() {
|
||||||
|
List<ClientRegistration> registrations = Arrays.asList(null, registration);
|
||||||
|
assertThatThrownBy(() -> new InMemoryReactiveClientRegistrationRepository(registrations))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenClientRegistrationIsNullThenIllegalArgumentException() {
|
public void constructorWhenClientRegistrationIsNullThenIllegalArgumentException() {
|
||||||
ClientRegistration registration = null;
|
assertThatThrownBy(() -> new InMemoryReactiveClientRegistrationRepository(registration, null))
|
||||||
assertThatThrownBy(() -> new InMemoryReactiveClientRegistrationRepository(registration))
|
|
||||||
.isInstanceOf(IllegalArgumentException.class);
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -277,7 +277,7 @@ public class DefaultOAuth2UserServiceTests {
|
|||||||
|
|
||||||
this.userService.loadUser(new OAuth2UserRequest(this.clientRegistration, this.accessToken));
|
this.userService.loadUser(new OAuth2UserRequest(this.clientRegistration, this.accessToken));
|
||||||
assertThat(this.server.takeRequest(1, TimeUnit.SECONDS).getHeader(HttpHeaders.ACCEPT))
|
assertThat(this.server.takeRequest(1, TimeUnit.SECONDS).getHeader(HttpHeaders.ACCEPT))
|
||||||
.isEqualTo(MediaType.APPLICATION_JSON_UTF8_VALUE);
|
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// gh-5500
|
// gh-5500
|
||||||
@@ -303,7 +303,7 @@ public class DefaultOAuth2UserServiceTests {
|
|||||||
this.userService.loadUser(new OAuth2UserRequest(this.clientRegistration, this.accessToken));
|
this.userService.loadUser(new OAuth2UserRequest(this.clientRegistration, this.accessToken));
|
||||||
RecordedRequest request = this.server.takeRequest();
|
RecordedRequest request = this.server.takeRequest();
|
||||||
assertThat(request.getMethod()).isEqualTo(HttpMethod.GET.name());
|
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());
|
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));
|
this.userService.loadUser(new OAuth2UserRequest(this.clientRegistration, this.accessToken));
|
||||||
RecordedRequest request = this.server.takeRequest();
|
RecordedRequest request = this.server.takeRequest();
|
||||||
assertThat(request.getMethod()).isEqualTo(HttpMethod.POST.name());
|
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.getHeader(HttpHeaders.CONTENT_TYPE)).contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||||
assertThat(request.getBody().readUtf8()).isEqualTo("access_token=" + this.accessToken.getTokenValue());
|
assertThat(request.getBody().readUtf8()).isEqualTo("access_token=" + this.accessToken.getTokenValue());
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -56,7 +56,7 @@ public class OAuth2UserRequestEntityConverterTests {
|
|||||||
clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri());
|
clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri());
|
||||||
|
|
||||||
HttpHeaders headers = requestEntity.getHeaders();
|
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(
|
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo(
|
||||||
"Bearer " + userRequest.getAccessToken().getTokenValue());
|
"Bearer " + userRequest.getAccessToken().getTokenValue());
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ public class OAuth2UserRequestEntityConverterTests {
|
|||||||
clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri());
|
clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri());
|
||||||
|
|
||||||
HttpHeaders headers = requestEntity.getHeaders();
|
HttpHeaders headers = requestEntity.getHeaders();
|
||||||
assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8);
|
assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON);
|
||||||
assertThat(headers.getContentType()).isEqualTo(
|
assertThat(headers.getContentType()).isEqualTo(
|
||||||
MediaType.valueOf(APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
|
MediaType.valueOf(APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -55,15 +55,15 @@ class OAuth2AccessTokenResponseBodyExtractor
|
|||||||
@Override
|
@Override
|
||||||
public Mono<OAuth2AccessTokenResponse> extract(ReactiveHttpInputMessage inputMessage,
|
public Mono<OAuth2AccessTokenResponse> extract(ReactiveHttpInputMessage inputMessage,
|
||||||
Context context) {
|
Context context) {
|
||||||
ParameterizedTypeReference<Map<String, String>> type = new ParameterizedTypeReference<Map<String, String>>() {};
|
ParameterizedTypeReference<Map<String, Object>> type = new ParameterizedTypeReference<Map<String, Object>>() {};
|
||||||
BodyExtractor<Mono<Map<String, String>>, ReactiveHttpInputMessage> delegate = BodyExtractors.toMono(type);
|
BodyExtractor<Mono<Map<String, Object>>, ReactiveHttpInputMessage> delegate = BodyExtractors.toMono(type);
|
||||||
return delegate.extract(inputMessage, context)
|
return delegate.extract(inputMessage, context)
|
||||||
.map(json -> parse(json))
|
.map(OAuth2AccessTokenResponseBodyExtractor::parse)
|
||||||
.flatMap(OAuth2AccessTokenResponseBodyExtractor::oauth2AccessTokenResponse)
|
.flatMap(OAuth2AccessTokenResponseBodyExtractor::oauth2AccessTokenResponse)
|
||||||
.map(OAuth2AccessTokenResponseBodyExtractor::oauth2AccessTokenResponse);
|
.map(OAuth2AccessTokenResponseBodyExtractor::oauth2AccessTokenResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TokenResponse parse(Map<String, String> json) {
|
private static TokenResponse parse(Map<String, Object> json) {
|
||||||
try {
|
try {
|
||||||
return TokenResponse.parse(new JSONObject(json));
|
return TokenResponse.parse(new JSONObject(json));
|
||||||
}
|
}
|
||||||
|
|||||||
+29
@@ -120,4 +120,33 @@ public class OAuth2BodyExtractorsTests {
|
|||||||
assertThat(result.getRefreshToken().getTokenValue()).isEqualTo("tGzv3JOkF0XG5Qx2TlKWIA");
|
assertThat(result.getRefreshToken().getTokenValue()).isEqualTo("tGzv3JOkF0XG5Qx2TlKWIA");
|
||||||
assertThat(result.getAdditionalParameters()).containsEntry("example_parameter", "example_value");
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-13
@@ -15,9 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.jwt;
|
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.OAuth2Error;
|
||||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
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",
|
"This iss claim is not equal to the configured issuer",
|
||||||
"https://tools.ietf.org/html/rfc6750#section-3.1");
|
"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
|
* Constructs a {@link JwtIssuerValidator} using the provided parameters
|
||||||
@@ -46,14 +43,7 @@ public final class JwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
|
|||||||
*/
|
*/
|
||||||
public JwtIssuerValidator(String issuer) {
|
public JwtIssuerValidator(String issuer) {
|
||||||
Assert.notNull(issuer, "issuer cannot be null");
|
Assert.notNull(issuer, "issuer cannot be null");
|
||||||
|
this.issuer = issuer;
|
||||||
try {
|
|
||||||
this.issuer = new URL(issuer);
|
|
||||||
} catch (MalformedURLException ex) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Invalid Issuer URL " + issuer + " : " + ex.getMessage(),
|
|
||||||
ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,7 +53,8 @@ public final class JwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
|
|||||||
public OAuth2TokenValidatorResult validate(Jwt token) {
|
public OAuth2TokenValidatorResult validate(Jwt token) {
|
||||||
Assert.notNull(token, "token cannot be null");
|
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();
|
return OAuth2TokenValidatorResult.success();
|
||||||
} else {
|
} else {
|
||||||
return OAuth2TokenValidatorResult.failure(INVALID_ISSUER);
|
return OAuth2TokenValidatorResult.failure(INVALID_ISSUER);
|
||||||
|
|||||||
+11
-24
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.security.oauth2.jwt;
|
package org.springframework.security.oauth2.jwt;
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -42,7 +41,7 @@ public final class MappedJwtClaimSetConverter
|
|||||||
implements Converter<Map<String, Object>, Map<String, Object>> {
|
implements Converter<Map<String, Object>, Map<String, Object>> {
|
||||||
|
|
||||||
private static final Converter<Object, Collection<String>> AUDIENCE_CONVERTER = new AudienceConverter();
|
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, String> STRING_CONVERTER = new StringConverter();
|
||||||
private static final Converter<Object, Instant> TEMPORAL_CONVERTER = new InstantConverter();
|
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
|
* 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.
|
* 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
|
@Override
|
||||||
public URL convert(Object source) {
|
public String convert(Object source) {
|
||||||
if (source == null) {
|
if (source == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source instanceof URL) {
|
if (source instanceof URL) {
|
||||||
return (URL) source;
|
return ((URL) source).toExternalForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source instanceof URI) {
|
if (source instanceof String && ((String) source).contains(":")) {
|
||||||
return toUrl((URI) source);
|
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());
|
return 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+27
-8
@@ -23,9 +23,6 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
|
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.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
@@ -73,14 +70,36 @@ public class JwtIssuerValidatorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void validateWhenJwtIsNullThenThrowsIllegalArgumentException() {
|
public void validateWhenJwtHasNoIssuerThenReturnsError() {
|
||||||
assertThatCode(() -> this.validator.validate(null))
|
Jwt jwt = new Jwt(
|
||||||
.isInstanceOf(IllegalArgumentException.class);
|
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
|
@Test
|
||||||
public void constructorWhenMalformedIssuerIsGivenThenThrowsIllegalArgumentException() {
|
public void validateWhenJwtIsNullThenThrowsIllegalArgumentException() {
|
||||||
assertThatCode(() -> new JwtIssuerValidator("issuer"))
|
assertThatCode(() -> this.validator.validate(null))
|
||||||
.isInstanceOf(IllegalArgumentException.class);
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-3
@@ -108,7 +108,7 @@ public class MappedJwtClaimSetConverterTests {
|
|||||||
assertThat(target.get(JwtClaimNames.AUD)).isEqualTo(Arrays.asList("audience"));
|
assertThat(target.get(JwtClaimNames.AUD)).isEqualTo(Arrays.asList("audience"));
|
||||||
assertThat(target.get(JwtClaimNames.EXP)).isEqualTo(Instant.ofEpochSecond(2000000000L));
|
assertThat(target.get(JwtClaimNames.EXP)).isEqualTo(Instant.ofEpochSecond(2000000000L));
|
||||||
assertThat(target.get(JwtClaimNames.IAT)).isEqualTo(Instant.ofEpochSecond(1000000000L));
|
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.NBF)).isEqualTo(Instant.ofEpochSecond(1000000000L));
|
||||||
assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234");
|
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.AUD)).isEqualTo(Arrays.asList("audience"));
|
||||||
assertThat(target.get(JwtClaimNames.EXP)).isEqualTo(Instant.ofEpochSecond(2000000000L));
|
assertThat(target.get(JwtClaimNames.EXP)).isEqualTo(Instant.ofEpochSecond(2000000000L));
|
||||||
assertThat(target.get(JwtClaimNames.IAT)).isEqualTo(Instant.ofEpochSecond(1000000000L));
|
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.NBF)).isEqualTo(Instant.ofEpochSecond(1000000000L));
|
||||||
assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234");
|
assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234");
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ public class MappedJwtClaimSetConverterTests {
|
|||||||
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
|
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
|
||||||
.withDefaults(Collections.emptyMap());
|
.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);
|
assertThatCode(() -> converter.convert(badIssuer)).isInstanceOf(IllegalStateException.class);
|
||||||
|
|
||||||
Map<String, Object> badIssuedAt = Collections.singletonMap(JwtClaimNames.IAT, "badly-formed-iat");
|
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);
|
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
|
@Test
|
||||||
public void constructWhenAnyParameterIsNullThenIllegalArgumentException() {
|
public void constructWhenAnyParameterIsNullThenIllegalArgumentException() {
|
||||||
assertThatCode(() -> new MappedJwtClaimSetConverter(null))
|
assertThatCode(() -> new MappedJwtClaimSetConverter(null))
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||||
|
|
||||||
ext['thymeleaf.version'] = '3.0.9.RELEASE'
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':spring-security-config')
|
compile project(':spring-security-config')
|
||||||
compile project(':spring-security-web')
|
compile project(':spring-security-web')
|
||||||
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
compile 'org.springframework.boot:spring-boot-starter-web'
|
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 project(':spring-security-test')
|
||||||
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE 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>
|
<head>
|
||||||
<title>Hello Spring Security</title>
|
<title>Hello Spring Security</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||||
|
|
||||||
ext['thymeleaf.version'] = '3.0.9.RELEASE'
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
compile 'org.springframework.boot:spring-boot-starter-web'
|
compile 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
|||||||
+1
-3
@@ -1,14 +1,12 @@
|
|||||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||||
|
|
||||||
ext['thymeleaf.version'] = '3.0.9.RELEASE'
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':spring-security-config')
|
compile project(':spring-security-config')
|
||||||
compile project(':spring-security-oauth2-client')
|
compile project(':spring-security-oauth2-client')
|
||||||
compile project(':spring-security-oauth2-jose')
|
compile project(':spring-security-oauth2-jose')
|
||||||
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
compile 'org.springframework.boot:spring-boot-starter-webflux'
|
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 project(':spring-security-test')
|
||||||
testCompile 'net.sourceforge.htmlunit:htmlunit'
|
testCompile 'net.sourceforge.htmlunit:htmlunit'
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||||
|
|
||||||
ext['thymeleaf.version'] = '3.0.9.RELEASE'
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':spring-security-config')
|
compile project(':spring-security-config')
|
||||||
compile project(':spring-security-oauth2-client')
|
compile project(':spring-security-oauth2-client')
|
||||||
compile project(':spring-security-oauth2-jose')
|
compile project(':spring-security-oauth2-jose')
|
||||||
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
compile 'org.springframework.boot:spring-boot-starter-web'
|
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 project(':spring-security-test')
|
||||||
testCompile 'net.sourceforge.htmlunit:htmlunit'
|
testCompile 'net.sourceforge.htmlunit:htmlunit'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE 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>
|
<head>
|
||||||
<title>Spring Security - OAuth 2.0 Login</title>
|
<title>Spring Security - OAuth 2.0 Login</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ dependencies {
|
|||||||
compile project(':spring-security-oauth2-jose')
|
compile project(':spring-security-oauth2-jose')
|
||||||
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
compile 'org.springframework.boot:spring-boot-starter-webflux'
|
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'
|
compile 'io.projectreactor.netty:reactor-netty'
|
||||||
|
|
||||||
testCompile project(':spring-security-test')
|
testCompile project(':spring-security-test')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE 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>
|
<head>
|
||||||
<title>OAuth2 WebClient Showcase</title>
|
<title>OAuth2 WebClient Showcase</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||||
|
|
||||||
ext['thymeleaf.version'] = '3.0.9.RELEASE'
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':spring-security-config')
|
compile project(':spring-security-config')
|
||||||
compile project(':spring-security-oauth2-client')
|
compile project(':spring-security-oauth2-client')
|
||||||
@@ -9,7 +7,7 @@ dependencies {
|
|||||||
compile 'org.springframework:spring-webflux'
|
compile 'org.springframework:spring-webflux'
|
||||||
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
compile 'org.springframework.boot:spring-boot-starter-web'
|
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'
|
compile 'io.projectreactor.netty:reactor-netty'
|
||||||
|
|
||||||
testCompile project(':spring-security-test')
|
testCompile project(':spring-security-test')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE 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>
|
<head>
|
||||||
<title>OAuth2 WebClient Showcase</title>
|
<title>OAuth2 WebClient Showcase</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ dependencies {
|
|||||||
compile project(':spring-security-web')
|
compile project(':spring-security-web')
|
||||||
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
compile 'org.springframework.boot:spring-boot-starter-webflux'
|
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 project(':spring-security-test')
|
||||||
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2002-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import org.springframework.security.web.server.csrf.CsrfToken;
|
|
||||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import static org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Rob Winch
|
|
||||||
* @since 5.0
|
|
||||||
*/
|
|
||||||
@ControllerAdvice
|
|
||||||
public class CsrfControllerAdvice {
|
|
||||||
@ModelAttribute
|
|
||||||
public Mono<CsrfToken> csrfToken(ServerWebExchange exchange) {
|
|
||||||
Mono<CsrfToken> csrfToken = exchange.getAttribute(CsrfToken.class.getName());
|
|
||||||
return csrfToken.doOnSuccess(token -> exchange.getAttributes().put(DEFAULT_CSRF_ATTR_NAME, token));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,6 +27,10 @@ dependencies {
|
|||||||
compile 'org.springframework:spring-webmvc'
|
compile 'org.springframework:spring-webmvc'
|
||||||
compile slf4jDependencies
|
compile slf4jDependencies
|
||||||
|
|
||||||
|
compile 'javax.xml.bind:jaxb-api'
|
||||||
|
compile 'com.sun.xml.bind:jaxb-core'
|
||||||
|
compile 'com.sun.xml.bind:jaxb-impl'
|
||||||
|
|
||||||
providedCompile 'javax.servlet.jsp:javax.servlet.jsp-api'
|
providedCompile 'javax.servlet.jsp:javax.servlet.jsp-api'
|
||||||
providedCompile 'javax.servlet:javax.servlet-api'
|
providedCompile 'javax.servlet:javax.servlet-api'
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ dependencies {
|
|||||||
compile 'org.hibernate:hibernate-validator'
|
compile 'org.hibernate:hibernate-validator'
|
||||||
compile slf4jDependencies
|
compile slf4jDependencies
|
||||||
|
|
||||||
|
compile 'javax.xml.bind:jaxb-api'
|
||||||
|
compile 'com.sun.xml.bind:jaxb-core'
|
||||||
|
compile 'com.sun.xml.bind:jaxb-impl'
|
||||||
|
|
||||||
providedCompile 'javax.servlet.jsp:javax.servlet.jsp-api'
|
providedCompile 'javax.servlet.jsp:javax.servlet.jsp-api'
|
||||||
providedCompile 'javax.servlet:javax.servlet-api'
|
providedCompile 'javax.servlet:javax.servlet-api'
|
||||||
|
|
||||||
|
|||||||
-47
@@ -1,47 +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.samples.config;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.security.web.FilterChainProxy;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Rob Winch
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
|
||||||
@ContextConfiguration
|
|
||||||
@WebAppConfiguration
|
|
||||||
public class SecurityConfigTests {
|
|
||||||
@Configuration
|
|
||||||
@ComponentScan(basePackages = "org.springframework.security.samples.config")
|
|
||||||
public static class Config {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private FilterChainProxy springSecurityFilterChain;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void securityConfigurationLoads() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import org.apache.tools.ant.filters.ReplaceTokens
|
|
||||||
|
|
||||||
apply plugin: 'io.spring.convention.spring-sample-war'
|
apply plugin: 'io.spring.convention.spring-sample-war'
|
||||||
|
|
||||||
def keystore = "$rootDir/samples/certificates/server.jks"
|
def keystore = "$rootDir/samples/certificates/server.jks"
|
||||||
@@ -8,6 +6,9 @@ def password = 'password'
|
|||||||
dependencies {
|
dependencies {
|
||||||
compile "org.jasig.cas:cas-server-webapp:4.0.0@war"
|
compile "org.jasig.cas:cas-server-webapp:4.0.0@war"
|
||||||
compile slf4jDependencies
|
compile slf4jDependencies
|
||||||
|
runtime 'org.aspectj:aspectjrt'
|
||||||
|
runtime 'org.aspectj:aspectjtools'
|
||||||
|
runtime 'org.aspectj:aspectjweaver'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.tasks.withType(org.gradle.api.tasks.bundling.War) { war ->
|
project.tasks.withType(org.gradle.api.tasks.bundling.War) { war ->
|
||||||
|
|||||||
+3
-2
@@ -44,6 +44,7 @@ public class ReactorContextTestExecutionListener
|
|||||||
extends DelegatingTestExecutionListener {
|
extends DelegatingTestExecutionListener {
|
||||||
|
|
||||||
private static final String HOOKS_CLASS_NAME = "reactor.core.publisher.Hooks";
|
private static final String HOOKS_CLASS_NAME = "reactor.core.publisher.Hooks";
|
||||||
|
private static final String CONTEXT_OPERATOR_KEY = SecurityContext.class.getName();
|
||||||
|
|
||||||
public ReactorContextTestExecutionListener() {
|
public ReactorContextTestExecutionListener() {
|
||||||
super(createDelegate());
|
super(createDelegate());
|
||||||
@@ -59,12 +60,12 @@ public class ReactorContextTestExecutionListener
|
|||||||
@Override
|
@Override
|
||||||
public void beforeTestMethod(TestContext testContext) throws Exception {
|
public void beforeTestMethod(TestContext testContext) throws Exception {
|
||||||
SecurityContext securityContext = TestSecurityContextHolder.getContext();
|
SecurityContext securityContext = TestSecurityContextHolder.getContext();
|
||||||
Hooks.onLastOperator(Operators.lift((s, sub) -> new SecuritySubContext<>(sub, securityContext)));
|
Hooks.onLastOperator(CONTEXT_OPERATOR_KEY, Operators.lift((s, sub) -> new SecuritySubContext<>(sub, securityContext)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTestMethod(TestContext testContext) throws Exception {
|
public void afterTestMethod(TestContext testContext) throws Exception {
|
||||||
Hooks.resetOnLastOperator();
|
Hooks.resetOnLastOperator(CONTEXT_OPERATOR_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class SecuritySubContext<T> implements CoreSubscriber<T> {
|
private static class SecuritySubContext<T> implements CoreSubscriber<T> {
|
||||||
|
|||||||
+11
@@ -174,6 +174,17 @@ public class ReactorContextTestExecutionListenerTests {
|
|||||||
assertThat(Mono.subscriberContext().block().isEmpty()).isTrue();
|
assertThat(Mono.subscriberContext().block().isEmpty()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void afterTestMethodWhenDifferentHookIsRegistered() throws Exception {
|
||||||
|
Object obj = new Object();
|
||||||
|
|
||||||
|
Hooks.onLastOperator("CUSTOM_HOOK", p -> Mono.just(obj));
|
||||||
|
this.listener.afterTestMethod(this.testContext);
|
||||||
|
|
||||||
|
Object result = Mono.subscriberContext().block();
|
||||||
|
assertThat(result).isEqualTo(obj);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void orderWhenComparedToWithSecurityContextTestExecutionListenerIsAfter() {
|
public void orderWhenComparedToWithSecurityContextTestExecutionListenerIsAfter() {
|
||||||
OrderComparator comparator = new OrderComparator();
|
OrderComparator comparator = new OrderComparator();
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ import java.util.regex.Matcher;
|
|||||||
* expression "CN=(.*?)(?:,|$)" matches the common name field. So
|
* expression "CN=(.*?)(?:,|$)" matches the common name field. So
|
||||||
* "CN=Jimi Hendrix, OU=..." will give a user name of "Jimi Hendrix".
|
* "CN=Jimi Hendrix, OU=..." will give a user name of "Jimi Hendrix".
|
||||||
* <p>
|
* <p>
|
||||||
* The matches are case insensitive. So "emailAddress=(.?)," will match
|
* The matches are case insensitive. So "emailAddress=(.*?)," will match
|
||||||
* "EMAILADDRESS=jimi@hendrix.org, CN=..." giving a user name "jimi@hendrix.org"
|
* "EMAILADDRESS=jimi@hendrix.org, CN=..." giving a user name "jimi@hendrix.org"
|
||||||
*
|
*
|
||||||
* @author Luke Taylor
|
* @author Luke Taylor
|
||||||
|
|||||||
+1
-1
@@ -272,7 +272,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (oauth2LoginEnabled) {
|
if (oauth2LoginEnabled) {
|
||||||
sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h3>");
|
sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h2>");
|
||||||
sb.append(createError(loginError, errorMsg));
|
sb.append(createError(loginError, errorMsg));
|
||||||
sb.append(createLogoutSuccess(logoutSuccess));
|
sb.append(createLogoutSuccess(logoutSuccess));
|
||||||
sb.append("<table class=\"table table-striped\">\n");
|
sb.append("<table class=\"table table-striped\">\n");
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -41,7 +41,7 @@ import org.springframework.web.context.request.async.CallableProcessingIntercept
|
|||||||
*/
|
*/
|
||||||
public final class SecurityContextCallableProcessingInterceptor extends
|
public final class SecurityContextCallableProcessingInterceptor extends
|
||||||
CallableProcessingInterceptorAdapter {
|
CallableProcessingInterceptorAdapter {
|
||||||
private SecurityContext securityContext;
|
private volatile SecurityContext securityContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link SecurityContextCallableProcessingInterceptor} that uses the
|
* Create a new {@link SecurityContextCallableProcessingInterceptor} that uses the
|
||||||
|
|||||||
+1
-1
@@ -60,7 +60,7 @@ public final class LazyCsrfTokenRepository implements CsrfTokenRepository {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Does nothing if the {@link CsrfToken} is not null. Saving is done only when the
|
* Does nothing if the {@link CsrfToken} is not null. Saving is done only when the
|
||||||
* {@link CsrfToken#getToken()} iis accessed from
|
* {@link CsrfToken#getToken()} is accessed from
|
||||||
* {@link #generateToken(HttpServletRequest)}. If it is null, then the save is
|
* {@link #generateToken(HttpServletRequest)}. If it is null, then the save is
|
||||||
* performed immediately.
|
* performed immediately.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2012 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -19,8 +19,12 @@ import java.io.IOException;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.RequestDispatcher;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletRequestWrapper;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.springframework.security.web.util.OnCommittedResponseWrapper;
|
import org.springframework.security.web.util.OnCommittedResponseWrapper;
|
||||||
@@ -33,6 +37,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
|||||||
* and X-Content-Type-Options.
|
* and X-Content-Type-Options.
|
||||||
*
|
*
|
||||||
* @author Marten Deinum
|
* @author Marten Deinum
|
||||||
|
* @author Josh Cummings
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@@ -62,8 +67,11 @@ public class HeaderWriterFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request,
|
HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request,
|
||||||
response, this.headerWriters);
|
response, this.headerWriters);
|
||||||
|
HeaderWriterRequest headerWriterRequest = new HeaderWriterRequest(request,
|
||||||
|
headerWriterResponse);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
filterChain.doFilter(request, headerWriterResponse);
|
filterChain.doFilter(headerWriterRequest, headerWriterResponse);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
headerWriterResponse.writeHeaders();
|
headerWriterResponse.writeHeaders();
|
||||||
@@ -106,4 +114,39 @@ public class HeaderWriterFilter extends OncePerRequestFilter {
|
|||||||
return (HttpServletResponse) getResponse();
|
return (HttpServletResponse) getResponse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class HeaderWriterRequest extends HttpServletRequestWrapper {
|
||||||
|
private final HeaderWriterResponse response;
|
||||||
|
|
||||||
|
HeaderWriterRequest(HttpServletRequest request, HeaderWriterResponse response) {
|
||||||
|
super(request);
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestDispatcher getRequestDispatcher(String path) {
|
||||||
|
return new HeaderWriterRequestDispatcher(super.getRequestDispatcher(path), this.response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class HeaderWriterRequestDispatcher implements RequestDispatcher {
|
||||||
|
private final RequestDispatcher delegate;
|
||||||
|
private final HeaderWriterResponse response;
|
||||||
|
|
||||||
|
HeaderWriterRequestDispatcher(RequestDispatcher delegate, HeaderWriterResponse response) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException {
|
||||||
|
this.delegate.forward(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException {
|
||||||
|
this.response.onResponseCommitted();
|
||||||
|
this.delegate.include(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
|||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* ObjectMapper mapper = new ObjectMapper();
|
* ObjectMapper mapper = new ObjectMapper();
|
||||||
* mapper.registerModule(new WebJackson2Module());
|
* mapper.registerModule(new WebServletJackson2Module());
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @author Jitendra Singh
|
* @author Jitendra Singh
|
||||||
* @see WebJackson2Module
|
* @see WebServletJackson2Module
|
||||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
|
|||||||
+2
-2
@@ -29,11 +29,11 @@ import org.springframework.security.web.savedrequest.DefaultSavedRequest;
|
|||||||
* <p>
|
* <p>
|
||||||
* <pre>
|
* <pre>
|
||||||
* ObjectMapper mapper = new ObjectMapper();
|
* ObjectMapper mapper = new ObjectMapper();
|
||||||
* mapper.registerModule(new WebJackson2Module());
|
* mapper.registerModule(new WebServletJackson2Module());
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @author Jitendra Singh
|
* @author Jitendra Singh
|
||||||
* @see WebJackson2Module
|
* @see WebServletJackson2Module
|
||||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ import com.fasterxml.jackson.annotation.*;
|
|||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* ObjectMapper mapper = new ObjectMapper();
|
* ObjectMapper mapper = new ObjectMapper();
|
||||||
* mapper.registerModule(new WebJackson2Module());
|
* mapper.registerModule(new WebServletJackson2Module());
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @author Jitendra Singh.
|
* @author Jitendra Singh.
|
||||||
* @see WebJackson2Module
|
* @see WebServletJackson2Module
|
||||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
|
|||||||
+2
-2
@@ -23,11 +23,11 @@ import com.fasterxml.jackson.annotation.*;
|
|||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* ObjectMapper mapper = new ObjectMapper();
|
* ObjectMapper mapper = new ObjectMapper();
|
||||||
* mapper.registerModule(new WebJackson2Module());
|
* mapper.registerModule(new WebServletJackson2Module());
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @author Jitendra Singh
|
* @author Jitendra Singh
|
||||||
* @see WebJackson2Module
|
* @see WebServletJackson2Module
|
||||||
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
* @see org.springframework.security.jackson2.SecurityJackson2Modules
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,23 +16,17 @@
|
|||||||
|
|
||||||
package org.springframework.security.web.jackson2;
|
package org.springframework.security.web.jackson2;
|
||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
|
|
||||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
|
||||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||||
import org.springframework.security.web.csrf.DefaultCsrfToken;
|
import org.springframework.security.web.csrf.DefaultCsrfToken;
|
||||||
import org.springframework.security.web.savedrequest.DefaultSavedRequest;
|
|
||||||
import org.springframework.security.web.savedrequest.SavedCookie;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.Version;
|
import com.fasterxml.jackson.core.Version;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jackson module for spring-security-web. This module register {@link CookieMixin},
|
* Jackson module for spring-security-web. This module register {@link DefaultCsrfTokenMixin} and
|
||||||
* {@link DefaultCsrfTokenMixin}, {@link DefaultSavedRequestMixin} and {@link WebAuthenticationDetailsMixin}. If no
|
* {@link PreAuthenticatedAuthenticationTokenMixin}. If no default typing enabled by default then it'll enable
|
||||||
* default typing enabled by default then it'll enable it because typing info is needed to properly serialize/deserialize objects.
|
* it because typing info is needed to properly serialize/deserialize objects.
|
||||||
* In order to use this module just add this module into your ObjectMapper configuration.
|
* In order to use this module just add this module into your ObjectMapper configuration.
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
@@ -53,12 +47,8 @@ public class WebJackson2Module extends SimpleModule {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setupModule(SetupContext context) {
|
public void setupModule(SetupContext context) {
|
||||||
SecurityJackson2Modules.enableDefaultTyping((ObjectMapper) context.getOwner());
|
SecurityJackson2Modules.enableDefaultTyping(context.getOwner());
|
||||||
context.setMixInAnnotations(Cookie.class, CookieMixin.class);
|
|
||||||
context.setMixInAnnotations(SavedCookie.class, SavedCookieMixin.class);
|
|
||||||
context.setMixInAnnotations(DefaultCsrfToken.class, DefaultCsrfTokenMixin.class);
|
context.setMixInAnnotations(DefaultCsrfToken.class, DefaultCsrfTokenMixin.class);
|
||||||
context.setMixInAnnotations(DefaultSavedRequest.class, DefaultSavedRequestMixin.class);
|
|
||||||
context.setMixInAnnotations(WebAuthenticationDetails.class, WebAuthenticationDetailsMixin.class);
|
|
||||||
context.setMixInAnnotations(PreAuthenticatedAuthenticationToken.class, PreAuthenticatedAuthenticationTokenMixin.class);
|
context.setMixInAnnotations(PreAuthenticatedAuthenticationToken.class, PreAuthenticatedAuthenticationTokenMixin.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015-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.web.jackson2;
|
||||||
|
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
|
||||||
|
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||||
|
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||||
|
import org.springframework.security.web.savedrequest.DefaultSavedRequest;
|
||||||
|
import org.springframework.security.web.savedrequest.SavedCookie;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.Version;
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jackson module for spring-security-web related to servlet. This module register {@link CookieMixin},
|
||||||
|
* {@link SavedCookieMixin}, {@link DefaultSavedRequestMixin} and {@link WebAuthenticationDetailsMixin}. If no
|
||||||
|
* default typing enabled by default then it'll enable it because typing info is needed to properly serialize/deserialize objects.
|
||||||
|
* In order to use this module just add this module into your ObjectMapper configuration.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* ObjectMapper mapper = new ObjectMapper();
|
||||||
|
* mapper.registerModule(new WebServletJackson2Module());
|
||||||
|
* </pre>
|
||||||
|
* <b>Note: use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get list of all security modules.</b>
|
||||||
|
*
|
||||||
|
* @author Boris Finkelshteyn
|
||||||
|
* @see SecurityJackson2Modules
|
||||||
|
* @since 5.1
|
||||||
|
*/
|
||||||
|
public class WebServletJackson2Module extends SimpleModule {
|
||||||
|
|
||||||
|
public WebServletJackson2Module() {
|
||||||
|
super(WebJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupModule(SetupContext context) {
|
||||||
|
SecurityJackson2Modules.enableDefaultTyping(context.getOwner());
|
||||||
|
context.setMixInAnnotations(Cookie.class, CookieMixin.class);
|
||||||
|
context.setMixInAnnotations(SavedCookie.class, SavedCookieMixin.class);
|
||||||
|
context.setMixInAnnotations(DefaultSavedRequest.class, DefaultSavedRequestMixin.class);
|
||||||
|
context.setMixInAnnotations(WebAuthenticationDetails.class, WebAuthenticationDetailsMixin.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -140,7 +140,7 @@ public class LoginPageGeneratingWebFilter implements WebFilter {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("<div class=\"container\"><h2 class=\"form-signin-heading\">Login with OAuth 2.0</h3>");
|
sb.append("<div class=\"container\"><h2 class=\"form-signin-heading\">Login with OAuth 2.0</h2>");
|
||||||
sb.append("<table class=\"table table-striped\">\n");
|
sb.append("<table class=\"table table-striped\">\n");
|
||||||
for (Map.Entry<String, String> clientAuthenticationUrlToClientName : oauth2AuthenticationUrlToClientName.entrySet()) {
|
for (Map.Entry<String, String> clientAuthenticationUrlToClientName : oauth2AuthenticationUrlToClientName.entrySet()) {
|
||||||
sb.append(" <tr><td>");
|
sb.append(" <tr><td>");
|
||||||
|
|||||||
+26
-2
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2013 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -18,6 +18,7 @@ package org.springframework.security.web.header;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
@@ -84,7 +85,9 @@ public class HeaderWriterFilterTests {
|
|||||||
|
|
||||||
verify(this.writer1).writeHeaders(request, response);
|
verify(this.writer1).writeHeaders(request, response);
|
||||||
verify(this.writer2).writeHeaders(request, response);
|
verify(this.writer2).writeHeaders(request, response);
|
||||||
assertThat(filterChain.getRequest()).isEqualTo(request); // verify the filterChain
|
HeaderWriterFilter.HeaderWriterRequest wrappedRequest = (HeaderWriterFilter.HeaderWriterRequest)
|
||||||
|
filterChain.getRequest();
|
||||||
|
assertThat(wrappedRequest.getRequest()).isEqualTo(request); // verify the filterChain
|
||||||
// continued
|
// continued
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,4 +115,25 @@ public class HeaderWriterFilterTests {
|
|||||||
|
|
||||||
verifyNoMoreInteractions(this.writer1);
|
verifyNoMoreInteractions(this.writer1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-5499
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenRequestContainsIncludeThenHeadersStillWritten() throws Exception {
|
||||||
|
HeaderWriterFilter filter = new HeaderWriterFilter(
|
||||||
|
Collections.singletonList(this.writer1));
|
||||||
|
|
||||||
|
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
|
||||||
|
|
||||||
|
filter.doFilter(mockRequest, mockResponse, (request, response) -> {
|
||||||
|
verifyZeroInteractions(HeaderWriterFilterTests.this.writer1);
|
||||||
|
|
||||||
|
request.getRequestDispatcher("/").include(request, response);
|
||||||
|
|
||||||
|
verify(HeaderWriterFilterTests.this.writer1).writeHeaders(
|
||||||
|
any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
});
|
||||||
|
|
||||||
|
verifyNoMoreInteractions(this.writer1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user