Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db4dd3f08e | ||
|
|
6e2f2fe8a4 | ||
|
|
061d9f8c18 | ||
|
|
59f77d034e | ||
|
|
bf24cfb19e | ||
|
|
77a9b2ebf3 | ||
|
|
d76d209124 | ||
|
|
58ad2d2c6c | ||
|
|
1a6b3e3e59 | ||
|
|
e61639bf7f | ||
|
|
7e2264204b | ||
|
|
43b44a1f77 | ||
|
|
19d6e97372 | ||
|
|
cff7b786de | ||
|
|
edf23562cb | ||
|
|
e7909d0cdd | ||
|
|
e49d4a79b4 | ||
|
|
7720e275e4 | ||
|
|
6a2c841d06 | ||
|
|
d7fe79d0ec | ||
|
|
40ca7a4654 | ||
|
|
06bf391bfa | ||
|
|
bfb5432b46 | ||
|
|
9818618ea3 | ||
|
|
cb09aef605 | ||
|
|
ebcdf7989d | ||
|
|
df8793c902 | ||
|
|
6c7486429c | ||
|
|
cf82c06502 | ||
|
|
a2167a5091 | ||
|
|
78d4bd0bad | ||
|
|
1ce77d3caa | ||
|
|
b7ddb837d6 | ||
|
|
dc94e5e161 | ||
|
|
18f8b3afaa | ||
|
|
601640e4fa | ||
|
|
af60f3d4d0 | ||
|
|
7b1b965c08 | ||
|
|
0c6b1251ce | ||
|
|
ad00d78e1a | ||
|
|
5471c94615 | ||
|
|
84cdf82c0d | ||
|
|
45283b42b4 | ||
|
|
142876068d | ||
|
|
ca94d02abc | ||
|
|
628c8bece3 | ||
|
|
f0013fc062 | ||
|
|
aa5133e170 | ||
|
|
5c31fb1b7e | ||
|
|
e5fdee3034 | ||
|
|
ab090445b3 | ||
|
|
8541f6be69 | ||
|
|
ae20f73676 | ||
|
|
4091d69d0c | ||
|
|
c3b254579c | ||
|
|
22bf1eb951 | ||
|
|
35ecdae190 | ||
|
|
b12ffe2ae4 | ||
|
|
117b312ac4 | ||
|
|
f7c84957bb | ||
|
|
5a030568ce | ||
|
|
72ec2633f8 | ||
|
|
0b95672c47 | ||
|
|
d51f12f5d2 | ||
|
|
f3a27101be |
@@ -17,6 +17,8 @@ apply plugin: 'io.spring.convention.root'
|
|||||||
group = 'org.springframework.security.experimental'
|
group = 'org.springframework.security.experimental'
|
||||||
description = 'Spring Authorization Server'
|
description = 'Spring Authorization Server'
|
||||||
|
|
||||||
|
ext.snapshotBuild = version.contains("SNAPSHOT")
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# This is a Gradle generated file for dependency locking.
|
||||||
|
# Manual edits can break the build and are not advised.
|
||||||
|
# This file is expected to be part of source control.
|
||||||
|
io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.4.2.RELEASE
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# This is a Gradle generated file for dependency locking.
|
||||||
|
# Manual edits can break the build and are not advised.
|
||||||
|
# This file is expected to be part of source control.
|
||||||
3
docs/manual/gradle/dependency-locks/compileOnly.lockfile
Normal file
3
docs/manual/gradle/dependency-locks/compileOnly.lockfile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# This is a Gradle generated file for dependency locking.
|
||||||
|
# Manual edits can break the build and are not advised.
|
||||||
|
# This file is expected to be part of source control.
|
||||||
3
docs/manual/gradle/dependency-locks/default.lockfile
Normal file
3
docs/manual/gradle/dependency-locks/default.lockfile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# This is a Gradle generated file for dependency locking.
|
||||||
|
# Manual edits can break the build and are not advised.
|
||||||
|
# This file is expected to be part of source control.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# This is a Gradle generated file for dependency locking.
|
||||||
|
# Manual edits can break the build and are not advised.
|
||||||
|
# This file is expected to be part of source control.
|
||||||
|
io.spring.docresources:spring-doc-resources:0.2.1.RELEASE
|
||||||
3
docs/manual/gradle/dependency-locks/runtime.lockfile
Normal file
3
docs/manual/gradle/dependency-locks/runtime.lockfile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# This is a Gradle generated file for dependency locking.
|
||||||
|
# Manual edits can break the build and are not advised.
|
||||||
|
# This file is expected to be part of source control.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# This is a Gradle generated file for dependency locking.
|
||||||
|
# Manual edits can break the build and are not advised.
|
||||||
|
# This file is expected to be part of source control.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# This is a Gradle generated file for dependency locking.
|
||||||
|
# Manual edits can break the build and are not advised.
|
||||||
|
# This file is expected to be part of source control.
|
||||||
13
docs/manual/gradle/dependency-locks/testCompile.lockfile
Normal file
13
docs/manual/gradle/dependency-locks/testCompile.lockfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# This is a Gradle generated file for dependency locking.
|
||||||
|
# Manual edits can break the build and are not advised.
|
||||||
|
# This file is expected to be part of source control.
|
||||||
|
junit:junit:4.13.1
|
||||||
|
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||||
|
net.bytebuddy:byte-buddy:1.10.15
|
||||||
|
org.assertj:assertj-core:3.18.0
|
||||||
|
org.hamcrest:hamcrest-core:1.3
|
||||||
|
org.mockito:mockito-core:3.6.0
|
||||||
|
org.objenesis:objenesis:3.1
|
||||||
|
org.springframework:spring-core:5.3.0
|
||||||
|
org.springframework:spring-jcl:5.3.0
|
||||||
|
org.springframework:spring-test:5.3.0
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# This is a Gradle generated file for dependency locking.
|
||||||
|
# Manual edits can break the build and are not advised.
|
||||||
|
# This file is expected to be part of source control.
|
||||||
|
junit:junit:4.13.1
|
||||||
|
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||||
|
net.bytebuddy:byte-buddy:1.10.15
|
||||||
|
org.assertj:assertj-core:3.18.0
|
||||||
|
org.hamcrest:hamcrest-core:1.3
|
||||||
|
org.mockito:mockito-core:3.6.0
|
||||||
|
org.objenesis:objenesis:3.1
|
||||||
|
org.springframework:spring-core:5.3.0
|
||||||
|
org.springframework:spring-jcl:5.3.0
|
||||||
|
org.springframework:spring-test:5.3.0
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# This is a Gradle generated file for dependency locking.
|
||||||
|
# Manual edits can break the build and are not advised.
|
||||||
|
# This file is expected to be part of source control.
|
||||||
13
docs/manual/gradle/dependency-locks/testRuntime.lockfile
Normal file
13
docs/manual/gradle/dependency-locks/testRuntime.lockfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# This is a Gradle generated file for dependency locking.
|
||||||
|
# Manual edits can break the build and are not advised.
|
||||||
|
# This file is expected to be part of source control.
|
||||||
|
junit:junit:4.13.1
|
||||||
|
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||||
|
net.bytebuddy:byte-buddy:1.10.15
|
||||||
|
org.assertj:assertj-core:3.18.0
|
||||||
|
org.hamcrest:hamcrest-core:1.3
|
||||||
|
org.mockito:mockito-core:3.6.0
|
||||||
|
org.objenesis:objenesis:3.1
|
||||||
|
org.springframework:spring-core:5.3.0
|
||||||
|
org.springframework:spring-jcl:5.3.0
|
||||||
|
org.springframework:spring-test:5.3.0
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# This is a Gradle generated file for dependency locking.
|
||||||
|
# Manual edits can break the build and are not advised.
|
||||||
|
# This file is expected to be part of source control.
|
||||||
|
junit:junit:4.13.1
|
||||||
|
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||||
|
net.bytebuddy:byte-buddy:1.10.15
|
||||||
|
org.assertj:assertj-core:3.18.0
|
||||||
|
org.hamcrest:hamcrest-core:1.3
|
||||||
|
org.mockito:mockito-core:3.6.0
|
||||||
|
org.objenesis:objenesis:3.1
|
||||||
|
org.springframework:spring-core:5.3.0
|
||||||
|
org.springframework:spring-jcl:5.3.0
|
||||||
|
org.springframework:spring-test:5.3.0
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
apply plugin: 'io.spring.convention.docs'
|
||||||
|
apply plugin: 'io.spring.convention.springdependencymangement'
|
||||||
|
apply plugin: 'io.spring.convention.dependency-set'
|
||||||
|
apply plugin: 'io.spring.convention.repository'
|
||||||
|
apply plugin: 'java'
|
||||||
|
|
||||||
|
asciidoctor {
|
||||||
|
attributes([stylesheet: 'css/style.css'])
|
||||||
|
resources {
|
||||||
|
from(sourceDir) {
|
||||||
|
include "css/**"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asciidoctorj {
|
||||||
|
def ghTag = snapshotBuild ? 'master' : project.version
|
||||||
|
def ghUrl = "https://github.com/spring-projects-experimental/spring-authorization-server/tree/$ghTag"
|
||||||
|
attributes 'spring-authorization-server-version' : project.version,
|
||||||
|
'spring-boot-version' : springBootVersion,
|
||||||
|
revnumber : project.version,
|
||||||
|
'gh-url': ghUrl,
|
||||||
|
'gh-samples-url': "$ghUrl/samples"
|
||||||
|
attributeProvider resolvedVersions(project.configurations.testCompile)
|
||||||
|
}
|
||||||
|
|
||||||
|
def resolvedVersions(Configuration configuration) {
|
||||||
|
return {
|
||||||
|
configuration.resolvedConfiguration
|
||||||
|
.resolvedArtifacts
|
||||||
|
.collectEntries { [(it.name + "-version"): it.moduleVersion.id.version] }
|
||||||
|
}
|
||||||
|
}
|
||||||
5
docs/manual/src/docs/asciidoc/css/style.css
Normal file
5
docs/manual/src/docs/asciidoc/css/style.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@import 'spring.css';
|
||||||
|
|
||||||
|
a code {
|
||||||
|
color: #097dff;
|
||||||
|
}
|
||||||
15
docs/manual/src/docs/asciidoc/index.adoc
Normal file
15
docs/manual/src/docs/asciidoc/index.adoc
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
= Spring Authorization Server Reference
|
||||||
|
Joe Grandja
|
||||||
|
:include-dir: _includes
|
||||||
|
:security-api-url: https://docs.spring.io/spring-authorization-server/site/docs/current/api/
|
||||||
|
:source-indent: 0
|
||||||
|
:tabsize: 4
|
||||||
|
:toc: left
|
||||||
|
|
||||||
|
== Preface
|
||||||
|
|
||||||
|
#TODO:# Document preface
|
||||||
|
|
||||||
|
== Introduction
|
||||||
|
|
||||||
|
#TODO:# Document introduction
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
version=0.0.1
|
version=0.0.3
|
||||||
springBootVersion=2.4.0-M2
|
springBootVersion=2.4.0-M3
|
||||||
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
|
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
if (!project.hasProperty("springVersion")) {
|
if (!project.hasProperty("springVersion")) {
|
||||||
ext.springVersion = "5.2.+"
|
ext.springVersion = "5.3.+"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project.hasProperty("springSecurityVersion")) {
|
if (!project.hasProperty("springSecurityVersion")) {
|
||||||
ext.springSecurityVersion = "5.3.+"
|
ext.springSecurityVersion = "5.4.+"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project.hasProperty("reactorVersion")) {
|
if (!project.hasProperty("reactorVersion")) {
|
||||||
ext.reactorVersion = "Dysprosium-SR+"
|
ext.reactorVersion = "2020.0.+"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project.hasProperty("locksDisabled")) {
|
if (!project.hasProperty("locksDisabled")) {
|
||||||
@@ -21,12 +21,12 @@ dependencyManagement {
|
|||||||
mavenBom "org.springframework:spring-framework-bom:$springVersion"
|
mavenBom "org.springframework:spring-framework-bom:$springVersion"
|
||||||
mavenBom "org.springframework.security:spring-security-bom:$springSecurityVersion"
|
mavenBom "org.springframework.security:spring-security-bom:$springSecurityVersion"
|
||||||
mavenBom "io.projectreactor:reactor-bom:$reactorVersion"
|
mavenBom "io.projectreactor:reactor-bom:$reactorVersion"
|
||||||
|
mavenBom "com.fasterxml.jackson:jackson-bom:2.+"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
dependency "com.nimbusds:oauth2-oidc-sdk:latest.release"
|
dependency "com.nimbusds:oauth2-oidc-sdk:latest.release"
|
||||||
dependency "com.nimbusds:nimbus-jose-jwt:latest.release"
|
dependency "com.nimbusds:nimbus-jose-jwt:latest.release"
|
||||||
dependency "com.fasterxml.jackson.core:jackson-databind:2.+"
|
|
||||||
dependency "javax.servlet:javax.servlet-api:4.+"
|
dependency "javax.servlet:javax.servlet-api:4.+"
|
||||||
dependency 'junit:junit:latest.release'
|
dependency 'junit:junit:latest.release'
|
||||||
dependency 'org.assertj:assertj-core:latest.release'
|
dependency 'org.assertj:assertj-core:latest.release'
|
||||||
@@ -36,32 +36,3 @@ dependencyManagement {
|
|||||||
dependency "com.jayway.jsonpath:json-path:2.+"
|
dependency "com.jayway.jsonpath:json-path:2.+"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
NOTE:
|
|
||||||
The latest `reactor-netty` dependency was split into `reactor-netty-core` and `reactor-netty-http`,
|
|
||||||
which resulted in the snapshot build to fail. The below configuration fixes it.
|
|
||||||
|
|
||||||
Reference:
|
|
||||||
- https://github.com/spring-projects/spring-security/issues/8909
|
|
||||||
- https://github.com/reactor/reactor-netty/issues/739#issuecomment-667047117
|
|
||||||
*/
|
|
||||||
if (reactorVersion.startsWith('20')) {
|
|
||||||
if (reactorVersion.endsWith('SNAPSHOT') || reactorVersion.endsWith('+')) {
|
|
||||||
ext.reactorLatestVersion = "latest.integration"
|
|
||||||
} else {
|
|
||||||
ext.reactorLatestVersion = "latest.release"
|
|
||||||
}
|
|
||||||
configurations {
|
|
||||||
all {
|
|
||||||
resolutionStrategy {
|
|
||||||
eachDependency { DependencyResolveDetails details ->
|
|
||||||
if (details.requested.name == 'reactor-netty') {
|
|
||||||
details.useTarget("${details.requested.group}:reactor-netty-http:${reactorLatestVersion}")
|
|
||||||
details.because("reactor-netty is now split into reactor-netty-core and reactor-netty-http")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
# This is a Gradle generated file for dependency locking.
|
||||||
# Manual edits can break the build and are not advised.
|
# Manual edits can break the build and are not advised.
|
||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
com.fasterxml.jackson.core:jackson-annotations:2.11.2
|
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-core:2.11.2
|
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-databind:2.11.2
|
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||||
|
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||||
com.nimbusds:nimbus-jose-jwt:8.20
|
com.nimbusds:nimbus-jose-jwt:9.1.2
|
||||||
net.minidev:accessors-smart:1.2
|
org.springframework.security:spring-security-config:5.4.1
|
||||||
net.minidev:json-smart:2.3
|
org.springframework.security:spring-security-core:5.4.1
|
||||||
org.ow2.asm:asm:5.0.4
|
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||||
org.springframework.security:spring-security-config:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||||
org.springframework.security:spring-security-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-web:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-core:5.3.4.RELEASE
|
org.springframework:spring-aop:5.3.0
|
||||||
org.springframework.security:spring-security-oauth2-jose:5.3.4.RELEASE
|
org.springframework:spring-beans:5.3.0
|
||||||
org.springframework.security:spring-security-web:5.3.4.RELEASE
|
org.springframework:spring-context:5.3.0
|
||||||
org.springframework:spring-aop:5.2.8.RELEASE
|
org.springframework:spring-core:5.3.0
|
||||||
org.springframework:spring-beans:5.2.8.RELEASE
|
org.springframework:spring-expression:5.3.0
|
||||||
org.springframework:spring-context:5.2.8.RELEASE
|
org.springframework:spring-jcl:5.3.0
|
||||||
org.springframework:spring-core:5.2.8.RELEASE
|
org.springframework:spring-web:5.3.0
|
||||||
org.springframework:spring-expression:5.2.8.RELEASE
|
|
||||||
org.springframework:spring-jcl:5.2.8.RELEASE
|
|
||||||
org.springframework:spring-web:5.2.8.RELEASE
|
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
# This is a Gradle generated file for dependency locking.
|
||||||
# Manual edits can break the build and are not advised.
|
# Manual edits can break the build and are not advised.
|
||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
com.fasterxml.jackson.core:jackson-annotations:2.11.2
|
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-core:2.11.2
|
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-databind:2.11.2
|
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||||
|
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||||
com.nimbusds:nimbus-jose-jwt:8.20
|
com.nimbusds:nimbus-jose-jwt:9.1.2
|
||||||
net.minidev:accessors-smart:1.2
|
org.springframework.security:spring-security-config:5.4.1
|
||||||
net.minidev:json-smart:2.3
|
org.springframework.security:spring-security-core:5.4.1
|
||||||
org.ow2.asm:asm:5.0.4
|
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||||
org.springframework.security:spring-security-config:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||||
org.springframework.security:spring-security-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-web:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-core:5.3.4.RELEASE
|
org.springframework:spring-aop:5.3.0
|
||||||
org.springframework.security:spring-security-oauth2-jose:5.3.4.RELEASE
|
org.springframework:spring-beans:5.3.0
|
||||||
org.springframework.security:spring-security-web:5.3.4.RELEASE
|
org.springframework:spring-context:5.3.0
|
||||||
org.springframework:spring-aop:5.2.8.RELEASE
|
org.springframework:spring-core:5.3.0
|
||||||
org.springframework:spring-beans:5.2.8.RELEASE
|
org.springframework:spring-expression:5.3.0
|
||||||
org.springframework:spring-context:5.2.8.RELEASE
|
org.springframework:spring-jcl:5.3.0
|
||||||
org.springframework:spring-core:5.2.8.RELEASE
|
org.springframework:spring-web:5.3.0
|
||||||
org.springframework:spring-expression:5.2.8.RELEASE
|
|
||||||
org.springframework:spring-jcl:5.2.8.RELEASE
|
|
||||||
org.springframework:spring-web:5.2.8.RELEASE
|
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
# This is a Gradle generated file for dependency locking.
|
||||||
# Manual edits can break the build and are not advised.
|
# Manual edits can break the build and are not advised.
|
||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
com.fasterxml.jackson.core:jackson-annotations:2.11.2
|
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-core:2.11.2
|
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-databind:2.11.2
|
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||||
|
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||||
com.nimbusds:nimbus-jose-jwt:8.20
|
com.nimbusds:nimbus-jose-jwt:9.1.2
|
||||||
net.minidev:accessors-smart:1.2
|
org.springframework.security:spring-security-config:5.4.1
|
||||||
net.minidev:json-smart:2.3
|
org.springframework.security:spring-security-core:5.4.1
|
||||||
org.ow2.asm:asm:5.0.4
|
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||||
org.springframework.security:spring-security-config:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||||
org.springframework.security:spring-security-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-web:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-core:5.3.4.RELEASE
|
org.springframework:spring-aop:5.3.0
|
||||||
org.springframework.security:spring-security-oauth2-jose:5.3.4.RELEASE
|
org.springframework:spring-beans:5.3.0
|
||||||
org.springframework.security:spring-security-web:5.3.4.RELEASE
|
org.springframework:spring-context:5.3.0
|
||||||
org.springframework:spring-aop:5.2.8.RELEASE
|
org.springframework:spring-core:5.3.0
|
||||||
org.springframework:spring-beans:5.2.8.RELEASE
|
org.springframework:spring-expression:5.3.0
|
||||||
org.springframework:spring-context:5.2.8.RELEASE
|
org.springframework:spring-jcl:5.3.0
|
||||||
org.springframework:spring-core:5.2.8.RELEASE
|
org.springframework:spring-web:5.3.0
|
||||||
org.springframework:spring-expression:5.2.8.RELEASE
|
|
||||||
org.springframework:spring-jcl:5.2.8.RELEASE
|
|
||||||
org.springframework:spring-web:5.2.8.RELEASE
|
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
# This is a Gradle generated file for dependency locking.
|
||||||
# Manual edits can break the build and are not advised.
|
# Manual edits can break the build and are not advised.
|
||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
com.fasterxml.jackson.core:jackson-annotations:2.11.2
|
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-core:2.11.2
|
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-databind:2.11.2
|
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||||
|
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||||
com.nimbusds:nimbus-jose-jwt:8.20
|
com.nimbusds:nimbus-jose-jwt:9.1.2
|
||||||
net.minidev:accessors-smart:1.2
|
org.springframework.security:spring-security-config:5.4.1
|
||||||
net.minidev:json-smart:2.3
|
org.springframework.security:spring-security-core:5.4.1
|
||||||
org.ow2.asm:asm:5.0.4
|
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||||
org.springframework.security:spring-security-config:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||||
org.springframework.security:spring-security-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-web:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-core:5.3.4.RELEASE
|
org.springframework:spring-aop:5.3.0
|
||||||
org.springframework.security:spring-security-oauth2-jose:5.3.4.RELEASE
|
org.springframework:spring-beans:5.3.0
|
||||||
org.springframework.security:spring-security-web:5.3.4.RELEASE
|
org.springframework:spring-context:5.3.0
|
||||||
org.springframework:spring-aop:5.2.8.RELEASE
|
org.springframework:spring-core:5.3.0
|
||||||
org.springframework:spring-beans:5.2.8.RELEASE
|
org.springframework:spring-expression:5.3.0
|
||||||
org.springframework:spring-context:5.2.8.RELEASE
|
org.springframework:spring-jcl:5.3.0
|
||||||
org.springframework:spring-core:5.2.8.RELEASE
|
org.springframework:spring-web:5.3.0
|
||||||
org.springframework:spring-expression:5.2.8.RELEASE
|
|
||||||
org.springframework:spring-jcl:5.2.8.RELEASE
|
|
||||||
org.springframework:spring-web:5.2.8.RELEASE
|
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
# This is a Gradle generated file for dependency locking.
|
||||||
# Manual edits can break the build and are not advised.
|
# Manual edits can break the build and are not advised.
|
||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
com.fasterxml.jackson.core:jackson-annotations:2.11.2
|
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-core:2.11.2
|
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-databind:2.11.2
|
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||||
|
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||||
com.nimbusds:nimbus-jose-jwt:8.20
|
com.nimbusds:nimbus-jose-jwt:9.1.2
|
||||||
net.minidev:accessors-smart:1.2
|
org.springframework.security:spring-security-config:5.4.1
|
||||||
net.minidev:json-smart:2.3
|
org.springframework.security:spring-security-core:5.4.1
|
||||||
org.ow2.asm:asm:5.0.4
|
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||||
org.springframework.security:spring-security-config:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||||
org.springframework.security:spring-security-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-web:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-core:5.3.4.RELEASE
|
org.springframework:spring-aop:5.3.0
|
||||||
org.springframework.security:spring-security-oauth2-jose:5.3.4.RELEASE
|
org.springframework:spring-beans:5.3.0
|
||||||
org.springframework.security:spring-security-web:5.3.4.RELEASE
|
org.springframework:spring-context:5.3.0
|
||||||
org.springframework:spring-aop:5.2.8.RELEASE
|
org.springframework:spring-core:5.3.0
|
||||||
org.springframework:spring-beans:5.2.8.RELEASE
|
org.springframework:spring-expression:5.3.0
|
||||||
org.springframework:spring-context:5.2.8.RELEASE
|
org.springframework:spring-jcl:5.3.0
|
||||||
org.springframework:spring-core:5.2.8.RELEASE
|
org.springframework:spring-web:5.3.0
|
||||||
org.springframework:spring-expression:5.2.8.RELEASE
|
|
||||||
org.springframework:spring-jcl:5.2.8.RELEASE
|
|
||||||
org.springframework:spring-web:5.2.8.RELEASE
|
|
||||||
|
|||||||
@@ -1,35 +1,36 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
# This is a Gradle generated file for dependency locking.
|
||||||
# Manual edits can break the build and are not advised.
|
# Manual edits can break the build and are not advised.
|
||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
com.fasterxml.jackson.core:jackson-annotations:2.11.2
|
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-core:2.11.2
|
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-databind:2.11.2
|
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||||
|
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||||
com.jayway.jsonpath:json-path:2.4.0
|
com.jayway.jsonpath:json-path:2.4.0
|
||||||
com.nimbusds:nimbus-jose-jwt:8.20
|
com.nimbusds:nimbus-jose-jwt:9.1.2
|
||||||
junit:junit:4.13
|
junit:junit:4.13.1
|
||||||
net.bytebuddy:byte-buddy-agent:1.10.13
|
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||||
net.bytebuddy:byte-buddy:1.10.13
|
net.bytebuddy:byte-buddy:1.10.15
|
||||||
net.minidev:accessors-smart:1.2
|
net.minidev:accessors-smart:1.2
|
||||||
net.minidev:json-smart:2.3
|
net.minidev:json-smart:2.3
|
||||||
org.assertj:assertj-core:3.16.1
|
org.assertj:assertj-core:3.18.0
|
||||||
org.hamcrest:hamcrest-core:1.3
|
org.hamcrest:hamcrest-core:1.3
|
||||||
org.mockito:mockito-core:3.5.2
|
org.mockito:mockito-core:3.6.0
|
||||||
org.objenesis:objenesis:3.1
|
org.objenesis:objenesis:3.1
|
||||||
org.ow2.asm:asm:5.0.4
|
org.ow2.asm:asm:5.0.4
|
||||||
org.slf4j:slf4j-api:1.7.25
|
org.slf4j:slf4j-api:1.7.25
|
||||||
org.springframework.security:spring-security-config:5.3.4.RELEASE
|
org.springframework.security:spring-security-config:5.4.1
|
||||||
org.springframework.security:spring-security-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-core:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-jose:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||||
org.springframework.security:spring-security-test:5.3.4.RELEASE
|
org.springframework.security:spring-security-test:5.4.1
|
||||||
org.springframework.security:spring-security-web:5.3.4.RELEASE
|
org.springframework.security:spring-security-web:5.4.1
|
||||||
org.springframework:spring-aop:5.2.8.RELEASE
|
org.springframework:spring-aop:5.3.0
|
||||||
org.springframework:spring-beans:5.2.8.RELEASE
|
org.springframework:spring-beans:5.3.0
|
||||||
org.springframework:spring-context:5.2.8.RELEASE
|
org.springframework:spring-context:5.3.0
|
||||||
org.springframework:spring-core:5.2.8.RELEASE
|
org.springframework:spring-core:5.3.0
|
||||||
org.springframework:spring-expression:5.2.8.RELEASE
|
org.springframework:spring-expression:5.3.0
|
||||||
org.springframework:spring-jcl:5.2.8.RELEASE
|
org.springframework:spring-jcl:5.3.0
|
||||||
org.springframework:spring-test:5.2.8.RELEASE
|
org.springframework:spring-test:5.3.0
|
||||||
org.springframework:spring-web:5.2.8.RELEASE
|
org.springframework:spring-web:5.3.0
|
||||||
org.springframework:spring-webmvc:5.2.8.RELEASE
|
org.springframework:spring-webmvc:5.3.0
|
||||||
|
|||||||
@@ -1,35 +1,36 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
# This is a Gradle generated file for dependency locking.
|
||||||
# Manual edits can break the build and are not advised.
|
# Manual edits can break the build and are not advised.
|
||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
com.fasterxml.jackson.core:jackson-annotations:2.11.2
|
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-core:2.11.2
|
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-databind:2.11.2
|
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||||
|
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||||
com.jayway.jsonpath:json-path:2.4.0
|
com.jayway.jsonpath:json-path:2.4.0
|
||||||
com.nimbusds:nimbus-jose-jwt:8.20
|
com.nimbusds:nimbus-jose-jwt:9.1.2
|
||||||
junit:junit:4.13
|
junit:junit:4.13.1
|
||||||
net.bytebuddy:byte-buddy-agent:1.10.13
|
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||||
net.bytebuddy:byte-buddy:1.10.13
|
net.bytebuddy:byte-buddy:1.10.15
|
||||||
net.minidev:accessors-smart:1.2
|
net.minidev:accessors-smart:1.2
|
||||||
net.minidev:json-smart:2.3
|
net.minidev:json-smart:2.3
|
||||||
org.assertj:assertj-core:3.16.1
|
org.assertj:assertj-core:3.18.0
|
||||||
org.hamcrest:hamcrest-core:1.3
|
org.hamcrest:hamcrest-core:1.3
|
||||||
org.mockito:mockito-core:3.5.2
|
org.mockito:mockito-core:3.6.0
|
||||||
org.objenesis:objenesis:3.1
|
org.objenesis:objenesis:3.1
|
||||||
org.ow2.asm:asm:5.0.4
|
org.ow2.asm:asm:5.0.4
|
||||||
org.slf4j:slf4j-api:1.7.25
|
org.slf4j:slf4j-api:1.7.25
|
||||||
org.springframework.security:spring-security-config:5.3.4.RELEASE
|
org.springframework.security:spring-security-config:5.4.1
|
||||||
org.springframework.security:spring-security-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-core:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-jose:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||||
org.springframework.security:spring-security-test:5.3.4.RELEASE
|
org.springframework.security:spring-security-test:5.4.1
|
||||||
org.springframework.security:spring-security-web:5.3.4.RELEASE
|
org.springframework.security:spring-security-web:5.4.1
|
||||||
org.springframework:spring-aop:5.2.8.RELEASE
|
org.springframework:spring-aop:5.3.0
|
||||||
org.springframework:spring-beans:5.2.8.RELEASE
|
org.springframework:spring-beans:5.3.0
|
||||||
org.springframework:spring-context:5.2.8.RELEASE
|
org.springframework:spring-context:5.3.0
|
||||||
org.springframework:spring-core:5.2.8.RELEASE
|
org.springframework:spring-core:5.3.0
|
||||||
org.springframework:spring-expression:5.2.8.RELEASE
|
org.springframework:spring-expression:5.3.0
|
||||||
org.springframework:spring-jcl:5.2.8.RELEASE
|
org.springframework:spring-jcl:5.3.0
|
||||||
org.springframework:spring-test:5.2.8.RELEASE
|
org.springframework:spring-test:5.3.0
|
||||||
org.springframework:spring-web:5.2.8.RELEASE
|
org.springframework:spring-web:5.3.0
|
||||||
org.springframework:spring-webmvc:5.2.8.RELEASE
|
org.springframework:spring-webmvc:5.3.0
|
||||||
|
|||||||
@@ -1,35 +1,36 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
# This is a Gradle generated file for dependency locking.
|
||||||
# Manual edits can break the build and are not advised.
|
# Manual edits can break the build and are not advised.
|
||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
com.fasterxml.jackson.core:jackson-annotations:2.11.2
|
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-core:2.11.2
|
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-databind:2.11.2
|
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||||
|
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||||
com.jayway.jsonpath:json-path:2.4.0
|
com.jayway.jsonpath:json-path:2.4.0
|
||||||
com.nimbusds:nimbus-jose-jwt:8.20
|
com.nimbusds:nimbus-jose-jwt:9.1.2
|
||||||
junit:junit:4.13
|
junit:junit:4.13.1
|
||||||
net.bytebuddy:byte-buddy-agent:1.10.13
|
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||||
net.bytebuddy:byte-buddy:1.10.13
|
net.bytebuddy:byte-buddy:1.10.15
|
||||||
net.minidev:accessors-smart:1.2
|
net.minidev:accessors-smart:1.2
|
||||||
net.minidev:json-smart:2.3
|
net.minidev:json-smart:2.3
|
||||||
org.assertj:assertj-core:3.16.1
|
org.assertj:assertj-core:3.18.0
|
||||||
org.hamcrest:hamcrest-core:1.3
|
org.hamcrest:hamcrest-core:1.3
|
||||||
org.mockito:mockito-core:3.5.2
|
org.mockito:mockito-core:3.6.0
|
||||||
org.objenesis:objenesis:3.1
|
org.objenesis:objenesis:3.1
|
||||||
org.ow2.asm:asm:5.0.4
|
org.ow2.asm:asm:5.0.4
|
||||||
org.slf4j:slf4j-api:1.7.25
|
org.slf4j:slf4j-api:1.7.25
|
||||||
org.springframework.security:spring-security-config:5.3.4.RELEASE
|
org.springframework.security:spring-security-config:5.4.1
|
||||||
org.springframework.security:spring-security-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-core:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-jose:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||||
org.springframework.security:spring-security-test:5.3.4.RELEASE
|
org.springframework.security:spring-security-test:5.4.1
|
||||||
org.springframework.security:spring-security-web:5.3.4.RELEASE
|
org.springframework.security:spring-security-web:5.4.1
|
||||||
org.springframework:spring-aop:5.2.8.RELEASE
|
org.springframework:spring-aop:5.3.0
|
||||||
org.springframework:spring-beans:5.2.8.RELEASE
|
org.springframework:spring-beans:5.3.0
|
||||||
org.springframework:spring-context:5.2.8.RELEASE
|
org.springframework:spring-context:5.3.0
|
||||||
org.springframework:spring-core:5.2.8.RELEASE
|
org.springframework:spring-core:5.3.0
|
||||||
org.springframework:spring-expression:5.2.8.RELEASE
|
org.springframework:spring-expression:5.3.0
|
||||||
org.springframework:spring-jcl:5.2.8.RELEASE
|
org.springframework:spring-jcl:5.3.0
|
||||||
org.springframework:spring-test:5.2.8.RELEASE
|
org.springframework:spring-test:5.3.0
|
||||||
org.springframework:spring-web:5.2.8.RELEASE
|
org.springframework:spring-web:5.3.0
|
||||||
org.springframework:spring-webmvc:5.2.8.RELEASE
|
org.springframework:spring-webmvc:5.3.0
|
||||||
|
|||||||
@@ -1,35 +1,36 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
# This is a Gradle generated file for dependency locking.
|
||||||
# Manual edits can break the build and are not advised.
|
# Manual edits can break the build and are not advised.
|
||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
com.fasterxml.jackson.core:jackson-annotations:2.11.2
|
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-core:2.11.2
|
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-databind:2.11.2
|
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||||
|
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||||
com.jayway.jsonpath:json-path:2.4.0
|
com.jayway.jsonpath:json-path:2.4.0
|
||||||
com.nimbusds:nimbus-jose-jwt:8.20
|
com.nimbusds:nimbus-jose-jwt:9.1.2
|
||||||
junit:junit:4.13
|
junit:junit:4.13.1
|
||||||
net.bytebuddy:byte-buddy-agent:1.10.13
|
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||||
net.bytebuddy:byte-buddy:1.10.13
|
net.bytebuddy:byte-buddy:1.10.15
|
||||||
net.minidev:accessors-smart:1.2
|
net.minidev:accessors-smart:1.2
|
||||||
net.minidev:json-smart:2.3
|
net.minidev:json-smart:2.3
|
||||||
org.assertj:assertj-core:3.16.1
|
org.assertj:assertj-core:3.18.0
|
||||||
org.hamcrest:hamcrest-core:1.3
|
org.hamcrest:hamcrest-core:1.3
|
||||||
org.mockito:mockito-core:3.5.2
|
org.mockito:mockito-core:3.6.0
|
||||||
org.objenesis:objenesis:3.1
|
org.objenesis:objenesis:3.1
|
||||||
org.ow2.asm:asm:5.0.4
|
org.ow2.asm:asm:5.0.4
|
||||||
org.slf4j:slf4j-api:1.7.25
|
org.slf4j:slf4j-api:1.7.25
|
||||||
org.springframework.security:spring-security-config:5.3.4.RELEASE
|
org.springframework.security:spring-security-config:5.4.1
|
||||||
org.springframework.security:spring-security-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-core:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-jose:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||||
org.springframework.security:spring-security-test:5.3.4.RELEASE
|
org.springframework.security:spring-security-test:5.4.1
|
||||||
org.springframework.security:spring-security-web:5.3.4.RELEASE
|
org.springframework.security:spring-security-web:5.4.1
|
||||||
org.springframework:spring-aop:5.2.8.RELEASE
|
org.springframework:spring-aop:5.3.0
|
||||||
org.springframework:spring-beans:5.2.8.RELEASE
|
org.springframework:spring-beans:5.3.0
|
||||||
org.springframework:spring-context:5.2.8.RELEASE
|
org.springframework:spring-context:5.3.0
|
||||||
org.springframework:spring-core:5.2.8.RELEASE
|
org.springframework:spring-core:5.3.0
|
||||||
org.springframework:spring-expression:5.2.8.RELEASE
|
org.springframework:spring-expression:5.3.0
|
||||||
org.springframework:spring-jcl:5.2.8.RELEASE
|
org.springframework:spring-jcl:5.3.0
|
||||||
org.springframework:spring-test:5.2.8.RELEASE
|
org.springframework:spring-test:5.3.0
|
||||||
org.springframework:spring-web:5.2.8.RELEASE
|
org.springframework:spring-web:5.3.0
|
||||||
org.springframework:spring-webmvc:5.2.8.RELEASE
|
org.springframework:spring-webmvc:5.3.0
|
||||||
|
|||||||
@@ -1,35 +1,36 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
# This is a Gradle generated file for dependency locking.
|
||||||
# Manual edits can break the build and are not advised.
|
# Manual edits can break the build and are not advised.
|
||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
com.fasterxml.jackson.core:jackson-annotations:2.11.2
|
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-core:2.11.2
|
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||||
com.fasterxml.jackson.core:jackson-databind:2.11.2
|
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||||
|
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||||
com.jayway.jsonpath:json-path:2.4.0
|
com.jayway.jsonpath:json-path:2.4.0
|
||||||
com.nimbusds:nimbus-jose-jwt:8.20
|
com.nimbusds:nimbus-jose-jwt:9.1.2
|
||||||
junit:junit:4.13
|
junit:junit:4.13.1
|
||||||
net.bytebuddy:byte-buddy-agent:1.10.13
|
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||||
net.bytebuddy:byte-buddy:1.10.13
|
net.bytebuddy:byte-buddy:1.10.15
|
||||||
net.minidev:accessors-smart:1.2
|
net.minidev:accessors-smart:1.2
|
||||||
net.minidev:json-smart:2.3
|
net.minidev:json-smart:2.3
|
||||||
org.assertj:assertj-core:3.16.1
|
org.assertj:assertj-core:3.18.0
|
||||||
org.hamcrest:hamcrest-core:1.3
|
org.hamcrest:hamcrest-core:1.3
|
||||||
org.mockito:mockito-core:3.5.2
|
org.mockito:mockito-core:3.6.0
|
||||||
org.objenesis:objenesis:3.1
|
org.objenesis:objenesis:3.1
|
||||||
org.ow2.asm:asm:5.0.4
|
org.ow2.asm:asm:5.0.4
|
||||||
org.slf4j:slf4j-api:1.7.25
|
org.slf4j:slf4j-api:1.7.25
|
||||||
org.springframework.security:spring-security-config:5.3.4.RELEASE
|
org.springframework.security:spring-security-config:5.4.1
|
||||||
org.springframework.security:spring-security-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-core:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-core:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||||
org.springframework.security:spring-security-oauth2-jose:5.3.4.RELEASE
|
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||||
org.springframework.security:spring-security-test:5.3.4.RELEASE
|
org.springframework.security:spring-security-test:5.4.1
|
||||||
org.springframework.security:spring-security-web:5.3.4.RELEASE
|
org.springframework.security:spring-security-web:5.4.1
|
||||||
org.springframework:spring-aop:5.2.8.RELEASE
|
org.springframework:spring-aop:5.3.0
|
||||||
org.springframework:spring-beans:5.2.8.RELEASE
|
org.springframework:spring-beans:5.3.0
|
||||||
org.springframework:spring-context:5.2.8.RELEASE
|
org.springframework:spring-context:5.3.0
|
||||||
org.springframework:spring-core:5.2.8.RELEASE
|
org.springframework:spring-core:5.3.0
|
||||||
org.springframework:spring-expression:5.2.8.RELEASE
|
org.springframework:spring-expression:5.3.0
|
||||||
org.springframework:spring-jcl:5.2.8.RELEASE
|
org.springframework:spring-jcl:5.3.0
|
||||||
org.springframework:spring-test:5.2.8.RELEASE
|
org.springframework:spring-test:5.3.0
|
||||||
org.springframework:spring-web:5.2.8.RELEASE
|
org.springframework:spring-web:5.3.0
|
||||||
org.springframework:spring-webmvc:5.2.8.RELEASE
|
org.springframework:spring-webmvc:5.3.0
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
|||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration(proxyBeanMethods = false)
|
||||||
public class OAuth2AuthorizationServerConfiguration {
|
public class OAuth2AuthorizationServerConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -15,11 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.config.annotation.web.configuration;
|
package org.springframework.security.config.annotation.web.configuration;
|
||||||
|
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
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.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
|
||||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
|
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
||||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
|
||||||
@@ -31,29 +30,29 @@ import static org.springframework.security.config.Customizer.withDefaults;
|
|||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
public class OAuth2AuthorizationServerSecurity extends WebSecurityConfigurerAdapter {
|
public class OAuth2AuthorizationServerSecurity extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
applyDefaultConfiguration(http);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
public static void applyDefaultConfiguration(HttpSecurity http) throws Exception {
|
||||||
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
|
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
|
||||||
new OAuth2AuthorizationServerConfigurer<>();
|
new OAuth2AuthorizationServerConfigurer<>();
|
||||||
|
RequestMatcher[] endpointMatchers = authorizationServerConfigurer
|
||||||
|
.getEndpointMatchers().toArray(new RequestMatcher[0]);
|
||||||
|
|
||||||
http
|
http
|
||||||
.requestMatcher(new OrRequestMatcher(authorizationServerConfigurer.getEndpointMatchers()))
|
.requestMatcher(new OrRequestMatcher(endpointMatchers))
|
||||||
.authorizeRequests(authorizeRequests ->
|
.authorizeRequests(authorizeRequests ->
|
||||||
authorizeRequests
|
authorizeRequests.anyRequest().authenticated()
|
||||||
.anyRequest().authenticated()
|
|
||||||
)
|
)
|
||||||
.formLogin(withDefaults())
|
.formLogin(withDefaults())
|
||||||
.csrf(csrf -> csrf.ignoringRequestMatchers(tokenEndpointMatcher()))
|
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointMatchers))
|
||||||
.apply(authorizationServerConfigurer);
|
.apply(authorizationServerConfigurer);
|
||||||
}
|
}
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
private static RequestMatcher tokenEndpointMatcher() {
|
|
||||||
return new AntPathRequestMatcher(
|
|
||||||
OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI,
|
|
||||||
HttpMethod.POST.name());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,15 +31,19 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
|
|||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationProvider;
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationProvider;
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider;
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationProvider;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||||
import org.springframework.security.oauth2.server.authorization.web.JwkSetEndpointFilter;
|
import org.springframework.security.oauth2.server.authorization.web.JwkSetEndpointFilter;
|
||||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
|
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
|
||||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter;
|
import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter;
|
||||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
|
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter;
|
||||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||||
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
||||||
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
|
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@@ -63,10 +67,17 @@ import java.util.Map;
|
|||||||
public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>>
|
public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>>
|
||||||
extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> {
|
extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> {
|
||||||
|
|
||||||
private final RequestMatcher authorizationEndpointMatcher = new AntPathRequestMatcher(
|
private final RequestMatcher authorizationEndpointMatcher = new OrRequestMatcher(
|
||||||
OAuth2AuthorizationEndpointFilter.DEFAULT_AUTHORIZATION_ENDPOINT_URI, HttpMethod.GET.name());
|
new AntPathRequestMatcher(
|
||||||
|
OAuth2AuthorizationEndpointFilter.DEFAULT_AUTHORIZATION_ENDPOINT_URI,
|
||||||
|
HttpMethod.GET.name()),
|
||||||
|
new AntPathRequestMatcher(
|
||||||
|
OAuth2AuthorizationEndpointFilter.DEFAULT_AUTHORIZATION_ENDPOINT_URI,
|
||||||
|
HttpMethod.POST.name()));
|
||||||
private final RequestMatcher tokenEndpointMatcher = new AntPathRequestMatcher(
|
private final RequestMatcher tokenEndpointMatcher = new AntPathRequestMatcher(
|
||||||
OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI, HttpMethod.POST.name());
|
OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI, HttpMethod.POST.name());
|
||||||
|
private final RequestMatcher tokenRevocationEndpointMatcher = new AntPathRequestMatcher(
|
||||||
|
OAuth2TokenRevocationEndpointFilter.DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI, HttpMethod.POST.name());
|
||||||
private final RequestMatcher jwkSetEndpointMatcher = new AntPathRequestMatcher(
|
private final RequestMatcher jwkSetEndpointMatcher = new AntPathRequestMatcher(
|
||||||
JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI, HttpMethod.GET.name());
|
JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI, HttpMethod.GET.name());
|
||||||
|
|
||||||
@@ -112,15 +123,16 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
|||||||
* @return a {@code List} of {@link RequestMatcher}'s for the authorization server endpoints
|
* @return a {@code List} of {@link RequestMatcher}'s for the authorization server endpoints
|
||||||
*/
|
*/
|
||||||
public List<RequestMatcher> getEndpointMatchers() {
|
public List<RequestMatcher> getEndpointMatchers() {
|
||||||
return Arrays.asList(this.authorizationEndpointMatcher,
|
return Arrays.asList(this.authorizationEndpointMatcher, this.tokenEndpointMatcher,
|
||||||
this.tokenEndpointMatcher, this.jwkSetEndpointMatcher);
|
this.tokenRevocationEndpointMatcher, this.jwkSetEndpointMatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(B builder) {
|
public void init(B builder) {
|
||||||
OAuth2ClientAuthenticationProvider clientAuthenticationProvider =
|
OAuth2ClientAuthenticationProvider clientAuthenticationProvider =
|
||||||
new OAuth2ClientAuthenticationProvider(
|
new OAuth2ClientAuthenticationProvider(
|
||||||
getRegisteredClientRepository(builder));
|
getRegisteredClientRepository(builder),
|
||||||
|
getAuthorizationService(builder));
|
||||||
builder.authenticationProvider(postProcess(clientAuthenticationProvider));
|
builder.authenticationProvider(postProcess(clientAuthenticationProvider));
|
||||||
|
|
||||||
NimbusJwsEncoder jwtEncoder = new NimbusJwsEncoder(getKeyManager(builder));
|
NimbusJwsEncoder jwtEncoder = new NimbusJwsEncoder(getKeyManager(builder));
|
||||||
@@ -132,17 +144,29 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
|||||||
jwtEncoder);
|
jwtEncoder);
|
||||||
builder.authenticationProvider(postProcess(authorizationCodeAuthenticationProvider));
|
builder.authenticationProvider(postProcess(authorizationCodeAuthenticationProvider));
|
||||||
|
|
||||||
|
OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider =
|
||||||
|
new OAuth2RefreshTokenAuthenticationProvider(
|
||||||
|
getAuthorizationService(builder),
|
||||||
|
jwtEncoder);
|
||||||
|
builder.authenticationProvider(postProcess(refreshTokenAuthenticationProvider));
|
||||||
|
|
||||||
OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider =
|
OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider =
|
||||||
new OAuth2ClientCredentialsAuthenticationProvider(
|
new OAuth2ClientCredentialsAuthenticationProvider(
|
||||||
getAuthorizationService(builder),
|
getAuthorizationService(builder),
|
||||||
jwtEncoder);
|
jwtEncoder);
|
||||||
builder.authenticationProvider(postProcess(clientCredentialsAuthenticationProvider));
|
builder.authenticationProvider(postProcess(clientCredentialsAuthenticationProvider));
|
||||||
|
|
||||||
|
OAuth2TokenRevocationAuthenticationProvider tokenRevocationAuthenticationProvider =
|
||||||
|
new OAuth2TokenRevocationAuthenticationProvider(
|
||||||
|
getAuthorizationService(builder));
|
||||||
|
builder.authenticationProvider(postProcess(tokenRevocationAuthenticationProvider));
|
||||||
|
|
||||||
ExceptionHandlingConfigurer<B> exceptionHandling = builder.getConfigurer(ExceptionHandlingConfigurer.class);
|
ExceptionHandlingConfigurer<B> exceptionHandling = builder.getConfigurer(ExceptionHandlingConfigurer.class);
|
||||||
if (exceptionHandling != null) {
|
if (exceptionHandling != null) {
|
||||||
// Register the default AuthenticationEntryPoint for the token endpoint
|
// Register the default AuthenticationEntryPoint for the token endpoint and token revocation endpoint
|
||||||
exceptionHandling.defaultAuthenticationEntryPointFor(
|
exceptionHandling.defaultAuthenticationEntryPointFor(
|
||||||
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), this.tokenEndpointMatcher);
|
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
|
||||||
|
new OrRequestMatcher(this.tokenEndpointMatcher, this.tokenRevocationEndpointMatcher));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,8 +177,10 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
|||||||
|
|
||||||
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
|
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
|
||||||
|
|
||||||
OAuth2ClientAuthenticationFilter clientAuthenticationFilter = new OAuth2ClientAuthenticationFilter(
|
OAuth2ClientAuthenticationFilter clientAuthenticationFilter =
|
||||||
authenticationManager, this.tokenEndpointMatcher);
|
new OAuth2ClientAuthenticationFilter(
|
||||||
|
authenticationManager,
|
||||||
|
new OrRequestMatcher(this.tokenEndpointMatcher, this.tokenRevocationEndpointMatcher));
|
||||||
builder.addFilterAfter(postProcess(clientAuthenticationFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
builder.addFilterAfter(postProcess(clientAuthenticationFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||||
|
|
||||||
OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
|
OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
|
||||||
@@ -168,6 +194,11 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
|||||||
authenticationManager,
|
authenticationManager,
|
||||||
getAuthorizationService(builder));
|
getAuthorizationService(builder));
|
||||||
builder.addFilterAfter(postProcess(tokenEndpointFilter), FilterSecurityInterceptor.class);
|
builder.addFilterAfter(postProcess(tokenEndpointFilter), FilterSecurityInterceptor.class);
|
||||||
|
|
||||||
|
OAuth2TokenRevocationEndpointFilter tokenRevocationEndpointFilter =
|
||||||
|
new OAuth2TokenRevocationEndpointFilter(
|
||||||
|
authenticationManager);
|
||||||
|
builder.addFilterAfter(postProcess(tokenRevocationEndpointFilter), OAuth2TokenEndpointFilter.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <B extends HttpSecurityBuilder<B>> RegisteredClientRepository getRegisteredClientRepository(B builder) {
|
private static <B extends HttpSecurityBuilder<B>> RegisteredClientRepository getRegisteredClientRepository(B builder) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
package org.springframework.security.crypto.keys;
|
package org.springframework.security.crypto.keys;
|
||||||
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.security.core.SpringSecurityCoreVersion2;
|
import org.springframework.security.oauth2.server.authorization.Version;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
@@ -35,7 +35,7 @@ import java.util.Objects;
|
|||||||
* @see KeyManager
|
* @see KeyManager
|
||||||
*/
|
*/
|
||||||
public final class ManagedKey implements Serializable {
|
public final class ManagedKey implements Serializable {
|
||||||
private static final long serialVersionUID = SpringSecurityCoreVersion2.SERIAL_VERSION_UID;
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
private Key key;
|
private Key key;
|
||||||
private PublicKey publicKey;
|
private PublicKey publicKey;
|
||||||
private String keyId;
|
private String keyId;
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
* This class is temporary and will be removed after upgrading to Spring Security 5.5.0 GA.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.3
|
||||||
|
* @see <a target="_blank" href="https://github.com/spring-projects/spring-security/issues/9184">Issue gh-9184</a>
|
||||||
|
*/
|
||||||
|
public interface OAuth2ErrorCodes2 extends OAuth2ErrorCodes {
|
||||||
|
|
||||||
|
String UNSUPPORTED_TOKEN_TYPE = "unsupported_token_type";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.core;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
* This class is temporary and will be removed after upgrading to Spring Security 5.5.0 GA.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.3
|
||||||
|
* @see <a target="_blank" href="https://github.com/spring-projects/spring-security/pull/9146">Issue gh-9146</a>
|
||||||
|
*/
|
||||||
|
public class OAuth2RefreshToken2 extends OAuth2RefreshToken {
|
||||||
|
private final Instant expiresAt;
|
||||||
|
|
||||||
|
public OAuth2RefreshToken2(String tokenValue, Instant issuedAt, Instant expiresAt) {
|
||||||
|
super(tokenValue, issuedAt);
|
||||||
|
this.expiresAt = expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant getExpiresAt() {
|
||||||
|
return this.expiresAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.core.endpoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
* This class is temporary and will be removed after upgrading to Spring Security 5.5.0 GA.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.3
|
||||||
|
* @see <a target="_blank" href="https://github.com/spring-projects/spring-security/issues/9183">Issue gh-9183</a>
|
||||||
|
*/
|
||||||
|
public interface OAuth2ParameterNames2 extends OAuth2ParameterNames {
|
||||||
|
|
||||||
|
String TOKEN = "token";
|
||||||
|
|
||||||
|
String TOKEN_TYPE_HINT = "token_type_hint";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -28,7 +28,6 @@ import com.nimbusds.jose.util.Base64;
|
|||||||
import com.nimbusds.jose.util.Base64URL;
|
import com.nimbusds.jose.util.Base64URL;
|
||||||
import com.nimbusds.jwt.JWTClaimsSet;
|
import com.nimbusds.jwt.JWTClaimsSet;
|
||||||
import com.nimbusds.jwt.SignedJWT;
|
import com.nimbusds.jwt.SignedJWT;
|
||||||
import net.minidev.json.JSONObject;
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.security.crypto.keys.KeyManager;
|
import org.springframework.security.crypto.keys.KeyManager;
|
||||||
import org.springframework.security.crypto.keys.ManagedKey;
|
import org.springframework.security.crypto.keys.ManagedKey;
|
||||||
@@ -213,7 +212,7 @@ public final class NimbusJwsEncoder implements JwtEncoder {
|
|||||||
Map<String, Object> jwk = headers.getJwk();
|
Map<String, Object> jwk = headers.getJwk();
|
||||||
if (!CollectionUtils.isEmpty(jwk)) {
|
if (!CollectionUtils.isEmpty(jwk)) {
|
||||||
try {
|
try {
|
||||||
builder.jwk(JWK.parse(new JSONObject(jwk)));
|
builder.jwk(JWK.parse(jwk));
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new JwtEncodingException(String.format(
|
throw new JwtEncodingException(String.format(
|
||||||
ENCODING_ERROR_MESSAGE_TEMPLATE,
|
ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
package org.springframework.security.oauth2.server.authorization;
|
package org.springframework.security.oauth2.server.authorization;
|
||||||
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.security.core.SpringSecurityCoreVersion2;
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@@ -27,6 +27,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
/**
|
/**
|
||||||
* An {@link OAuth2AuthorizationService} that stores {@link OAuth2Authorization}'s in-memory.
|
* An {@link OAuth2AuthorizationService} that stores {@link OAuth2Authorization}'s in-memory.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* <b>NOTE:</b> This implementation should ONLY be used during development/testing.
|
||||||
|
*
|
||||||
* @author Krisztian Toth
|
* @author Krisztian Toth
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
@@ -43,6 +46,14 @@ public final class InMemoryOAuth2AuthorizationService implements OAuth2Authoriza
|
|||||||
this.authorizations.put(authorizationId, authorization);
|
this.authorizations.put(authorizationId, authorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(OAuth2Authorization authorization) {
|
||||||
|
Assert.notNull(authorization, "authorization cannot be null");
|
||||||
|
OAuth2AuthorizationId authorizationId = new OAuth2AuthorizationId(
|
||||||
|
authorization.getRegisteredClientId(), authorization.getPrincipalName());
|
||||||
|
this.authorizations.remove(authorizationId, authorization);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OAuth2Authorization findByToken(String token, @Nullable TokenType tokenType) {
|
public OAuth2Authorization findByToken(String token, @Nullable TokenType tokenType) {
|
||||||
Assert.hasText(token, "token cannot be empty");
|
Assert.hasText(token, "token cannot be empty");
|
||||||
@@ -53,17 +64,24 @@ public final class InMemoryOAuth2AuthorizationService implements OAuth2Authoriza
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasToken(OAuth2Authorization authorization, String token, TokenType tokenType) {
|
private boolean hasToken(OAuth2Authorization authorization, String token, TokenType tokenType) {
|
||||||
if (TokenType.AUTHORIZATION_CODE.equals(tokenType)) {
|
if (OAuth2AuthorizationAttributeNames.STATE.equals(tokenType.getValue())) {
|
||||||
return token.equals(authorization.getAttribute(OAuth2AuthorizationAttributeNames.CODE));
|
return token.equals(authorization.getAttribute(OAuth2AuthorizationAttributeNames.STATE));
|
||||||
|
} else if (TokenType.AUTHORIZATION_CODE.equals(tokenType)) {
|
||||||
|
OAuth2AuthorizationCode authorizationCode = authorization.getTokens().getToken(OAuth2AuthorizationCode.class);
|
||||||
|
return authorizationCode != null && authorizationCode.getTokenValue().equals(token);
|
||||||
} else if (TokenType.ACCESS_TOKEN.equals(tokenType)) {
|
} else if (TokenType.ACCESS_TOKEN.equals(tokenType)) {
|
||||||
return authorization.getAccessToken() != null &&
|
return authorization.getTokens().getAccessToken() != null &&
|
||||||
authorization.getAccessToken().getTokenValue().equals(token);
|
authorization.getTokens().getAccessToken().getTokenValue().equals(token);
|
||||||
|
} else if (TokenType.REFRESH_TOKEN.equals(tokenType)) {
|
||||||
|
return authorization.getTokens().getRefreshToken() != null &&
|
||||||
|
authorization.getTokens().getRefreshToken().getTokenValue().equals(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class OAuth2AuthorizationId implements Serializable {
|
private static class OAuth2AuthorizationId implements Serializable {
|
||||||
private static final long serialVersionUID = SpringSecurityCoreVersion2.SERIAL_VERSION_UID;
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
private final String registeredClientId;
|
private final String registeredClientId;
|
||||||
private final String principalName;
|
private final String principalName;
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.server.authorization;
|
package org.springframework.security.oauth2.server.authorization;
|
||||||
|
|
||||||
import org.springframework.security.core.SpringSecurityCoreVersion2;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@@ -36,13 +36,17 @@ import java.util.function.Consumer;
|
|||||||
* @author Krisztian Toth
|
* @author Krisztian Toth
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
* @see RegisteredClient
|
* @see RegisteredClient
|
||||||
* @see OAuth2AccessToken
|
* @see OAuth2Tokens
|
||||||
*/
|
*/
|
||||||
public class OAuth2Authorization implements Serializable {
|
public class OAuth2Authorization implements Serializable {
|
||||||
private static final long serialVersionUID = SpringSecurityCoreVersion2.SERIAL_VERSION_UID;
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
private String registeredClientId;
|
private String registeredClientId;
|
||||||
private String principalName;
|
private String principalName;
|
||||||
|
private OAuth2Tokens tokens;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
private OAuth2AccessToken accessToken;
|
private OAuth2AccessToken accessToken;
|
||||||
|
|
||||||
private Map<String, Object> attributes;
|
private Map<String, Object> attributes;
|
||||||
|
|
||||||
protected OAuth2Authorization() {
|
protected OAuth2Authorization() {
|
||||||
@@ -66,13 +70,23 @@ public class OAuth2Authorization implements Serializable {
|
|||||||
return this.principalName;
|
return this.principalName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link OAuth2Tokens}.
|
||||||
|
*
|
||||||
|
* @return the {@link OAuth2Tokens}
|
||||||
|
*/
|
||||||
|
public OAuth2Tokens getTokens() {
|
||||||
|
return this.tokens;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link OAuth2AccessToken access token} credential.
|
* Returns the {@link OAuth2AccessToken access token} credential.
|
||||||
*
|
*
|
||||||
* @return the {@link OAuth2AccessToken}
|
* @return the {@link OAuth2AccessToken}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public OAuth2AccessToken getAccessToken() {
|
public OAuth2AccessToken getAccessToken() {
|
||||||
return this.accessToken;
|
return getTokens().getAccessToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -108,13 +122,13 @@ public class OAuth2Authorization implements Serializable {
|
|||||||
OAuth2Authorization that = (OAuth2Authorization) obj;
|
OAuth2Authorization that = (OAuth2Authorization) obj;
|
||||||
return Objects.equals(this.registeredClientId, that.registeredClientId) &&
|
return Objects.equals(this.registeredClientId, that.registeredClientId) &&
|
||||||
Objects.equals(this.principalName, that.principalName) &&
|
Objects.equals(this.principalName, that.principalName) &&
|
||||||
Objects.equals(this.accessToken, that.accessToken) &&
|
Objects.equals(this.tokens, that.tokens) &&
|
||||||
Objects.equals(this.attributes, that.attributes);
|
Objects.equals(this.attributes, that.attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(this.registeredClientId, this.principalName, this.accessToken, this.attributes);
|
return Objects.hash(this.registeredClientId, this.principalName, this.tokens, this.attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,7 +152,7 @@ public class OAuth2Authorization implements Serializable {
|
|||||||
Assert.notNull(authorization, "authorization cannot be null");
|
Assert.notNull(authorization, "authorization cannot be null");
|
||||||
return new Builder(authorization.getRegisteredClientId())
|
return new Builder(authorization.getRegisteredClientId())
|
||||||
.principalName(authorization.getPrincipalName())
|
.principalName(authorization.getPrincipalName())
|
||||||
.accessToken(authorization.getAccessToken())
|
.tokens(OAuth2Tokens.from(authorization.getTokens()).build())
|
||||||
.attributes(attrs -> attrs.putAll(authorization.getAttributes()));
|
.attributes(attrs -> attrs.putAll(authorization.getAttributes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,10 +160,14 @@ public class OAuth2Authorization implements Serializable {
|
|||||||
* A builder for {@link OAuth2Authorization}.
|
* A builder for {@link OAuth2Authorization}.
|
||||||
*/
|
*/
|
||||||
public static class Builder implements Serializable {
|
public static class Builder implements Serializable {
|
||||||
private static final long serialVersionUID = SpringSecurityCoreVersion2.SERIAL_VERSION_UID;
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
private String registeredClientId;
|
private String registeredClientId;
|
||||||
private String principalName;
|
private String principalName;
|
||||||
|
private OAuth2Tokens tokens;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
private OAuth2AccessToken accessToken;
|
private OAuth2AccessToken accessToken;
|
||||||
|
|
||||||
private Map<String, Object> attributes = new HashMap<>();
|
private Map<String, Object> attributes = new HashMap<>();
|
||||||
|
|
||||||
protected Builder(String registeredClientId) {
|
protected Builder(String registeredClientId) {
|
||||||
@@ -167,12 +185,24 @@ public class OAuth2Authorization implements Serializable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link OAuth2Tokens}.
|
||||||
|
*
|
||||||
|
* @param tokens the {@link OAuth2Tokens}
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder tokens(OAuth2Tokens tokens) {
|
||||||
|
this.tokens = tokens;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link OAuth2AccessToken access token} credential.
|
* Sets the {@link OAuth2AccessToken access token} credential.
|
||||||
*
|
*
|
||||||
* @param accessToken the {@link OAuth2AccessToken}
|
* @param accessToken the {@link OAuth2AccessToken}
|
||||||
* @return the {@link Builder}
|
* @return the {@link Builder}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public Builder accessToken(OAuth2AccessToken accessToken) {
|
public Builder accessToken(OAuth2AccessToken accessToken) {
|
||||||
this.accessToken = accessToken;
|
this.accessToken = accessToken;
|
||||||
return this;
|
return this;
|
||||||
@@ -215,7 +245,14 @@ public class OAuth2Authorization implements Serializable {
|
|||||||
OAuth2Authorization authorization = new OAuth2Authorization();
|
OAuth2Authorization authorization = new OAuth2Authorization();
|
||||||
authorization.registeredClientId = this.registeredClientId;
|
authorization.registeredClientId = this.registeredClientId;
|
||||||
authorization.principalName = this.principalName;
|
authorization.principalName = this.principalName;
|
||||||
authorization.accessToken = this.accessToken;
|
if (this.tokens == null) {
|
||||||
|
OAuth2Tokens.Builder builder = OAuth2Tokens.builder();
|
||||||
|
if (this.accessToken != null) {
|
||||||
|
builder.accessToken(this.accessToken);
|
||||||
|
}
|
||||||
|
this.tokens = builder.build();
|
||||||
|
}
|
||||||
|
authorization.tokens = this.tokens;
|
||||||
authorization.attributes = Collections.unmodifiableMap(this.attributes);
|
authorization.attributes = Collections.unmodifiableMap(this.attributes);
|
||||||
return authorization;
|
return authorization;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,15 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|||||||
*/
|
*/
|
||||||
public interface OAuth2AuthorizationAttributeNames {
|
public interface OAuth2AuthorizationAttributeNames {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the attribute used for correlating the user consent request/response.
|
||||||
|
*/
|
||||||
|
String STATE = OAuth2Authorization.class.getName().concat(".STATE");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the attribute used for the {@link OAuth2ParameterNames#CODE} parameter.
|
* The name of the attribute used for the {@link OAuth2ParameterNames#CODE} parameter.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
String CODE = OAuth2Authorization.class.getName().concat(".CODE");
|
String CODE = OAuth2Authorization.class.getName().concat(".CODE");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,6 +46,11 @@ public interface OAuth2AuthorizationAttributeNames {
|
|||||||
*/
|
*/
|
||||||
String AUTHORIZATION_REQUEST = OAuth2Authorization.class.getName().concat(".AUTHORIZATION_REQUEST");
|
String AUTHORIZATION_REQUEST = OAuth2Authorization.class.getName().concat(".AUTHORIZATION_REQUEST");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the attribute used for the authorized scope(s).
|
||||||
|
*/
|
||||||
|
String AUTHORIZED_SCOPES = OAuth2Authorization.class.getName().concat(".AUTHORIZED_SCOPES");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the attribute used for the attributes/claims of the {@link OAuth2AccessToken}.
|
* The name of the attribute used for the attributes/claims of the {@link OAuth2AccessToken}.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ public interface OAuth2AuthorizationService {
|
|||||||
*/
|
*/
|
||||||
void save(OAuth2Authorization authorization);
|
void save(OAuth2Authorization authorization);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the {@link OAuth2Authorization}.
|
||||||
|
*
|
||||||
|
* @param authorization the {@link OAuth2Authorization}
|
||||||
|
*/
|
||||||
|
void remove(OAuth2Authorization authorization);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link OAuth2Authorization} containing the provided {@code token},
|
* Returns the {@link OAuth2Authorization} containing the provided {@code token},
|
||||||
* or {@code null} if not found.
|
* or {@code null} if not found.
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.server.authorization;
|
package org.springframework.security.oauth2.server.authorization;
|
||||||
|
|
||||||
import org.springframework.security.core.SpringSecurityCoreVersion2;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@@ -24,8 +23,9 @@ import java.io.Serializable;
|
|||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
*/
|
*/
|
||||||
public final class TokenType implements Serializable {
|
public final class TokenType implements Serializable {
|
||||||
private static final long serialVersionUID = SpringSecurityCoreVersion2.SERIAL_VERSION_UID;
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
public static final TokenType ACCESS_TOKEN = new TokenType("access_token");
|
public static final TokenType ACCESS_TOKEN = new TokenType("access_token");
|
||||||
|
public static final TokenType REFRESH_TOKEN = new TokenType("refresh_token");
|
||||||
public static final TokenType AUTHORIZATION_CODE = new TokenType("authorization_code");
|
public static final TokenType AUTHORIZATION_CODE = new TokenType("authorization_code");
|
||||||
private final String value;
|
private final String value;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
* 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.core;
|
package org.springframework.security.oauth2.server.authorization;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal class used for serialization across Spring Security Authorization Server classes.
|
* Internal class used for serialization across Spring Security Authorization Server classes.
|
||||||
@@ -21,10 +21,10 @@ package org.springframework.security.core;
|
|||||||
* @author Anoop Garlapati
|
* @author Anoop Garlapati
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
public final class SpringSecurityCoreVersion2 {
|
public final class Version {
|
||||||
private static final int MAJOR = 0;
|
private static final int MAJOR = 0;
|
||||||
private static final int MINOR = 0;
|
private static final int MINOR = 0;
|
||||||
private static final int PATCH = 1;
|
private static final int PATCH = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global Serialization value for Spring Security Authorization Server classes.
|
* Global Serialization value for Spring Security Authorization Server classes.
|
||||||
@@ -15,32 +15,36 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.SpringSecurityCoreVersion2;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.Version;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link Authentication} implementation used when issuing an OAuth 2.0 Access Token.
|
* An {@link Authentication} implementation used when issuing an
|
||||||
|
* OAuth 2.0 Access Token and (optional) Refresh Token.
|
||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @author Madhu Bhat
|
* @author Madhu Bhat
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
* @see AbstractAuthenticationToken
|
* @see AbstractAuthenticationToken
|
||||||
* @see OAuth2AuthorizationCodeAuthenticationProvider
|
|
||||||
* @see RegisteredClient
|
* @see RegisteredClient
|
||||||
* @see OAuth2AccessToken
|
* @see OAuth2AccessToken
|
||||||
|
* @see OAuth2RefreshToken
|
||||||
* @see OAuth2ClientAuthenticationToken
|
* @see OAuth2ClientAuthenticationToken
|
||||||
*/
|
*/
|
||||||
public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthenticationToken {
|
public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthenticationToken {
|
||||||
private static final long serialVersionUID = SpringSecurityCoreVersion2.SERIAL_VERSION_UID;
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
private final RegisteredClient registeredClient;
|
private final RegisteredClient registeredClient;
|
||||||
private final Authentication clientPrincipal;
|
private final Authentication clientPrincipal;
|
||||||
private final OAuth2AccessToken accessToken;
|
private final OAuth2AccessToken accessToken;
|
||||||
|
private final OAuth2RefreshToken refreshToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an {@code OAuth2AccessTokenAuthenticationToken} using the provided parameters.
|
* Constructs an {@code OAuth2AccessTokenAuthenticationToken} using the provided parameters.
|
||||||
@@ -51,6 +55,19 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication
|
|||||||
*/
|
*/
|
||||||
public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient,
|
public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient,
|
||||||
Authentication clientPrincipal, OAuth2AccessToken accessToken) {
|
Authentication clientPrincipal, OAuth2AccessToken accessToken) {
|
||||||
|
this(registeredClient, clientPrincipal, accessToken, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2AccessTokenAuthenticationToken} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param registeredClient the registered client
|
||||||
|
* @param clientPrincipal the authenticated client principal
|
||||||
|
* @param accessToken the access token
|
||||||
|
* @param refreshToken the refresh token
|
||||||
|
*/
|
||||||
|
public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient, Authentication clientPrincipal,
|
||||||
|
OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken) {
|
||||||
super(Collections.emptyList());
|
super(Collections.emptyList());
|
||||||
Assert.notNull(registeredClient, "registeredClient cannot be null");
|
Assert.notNull(registeredClient, "registeredClient cannot be null");
|
||||||
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
|
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
|
||||||
@@ -58,6 +75,7 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication
|
|||||||
this.registeredClient = registeredClient;
|
this.registeredClient = registeredClient;
|
||||||
this.clientPrincipal = clientPrincipal;
|
this.clientPrincipal = clientPrincipal;
|
||||||
this.accessToken = accessToken;
|
this.accessToken = accessToken;
|
||||||
|
this.refreshToken = refreshToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -87,4 +105,14 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication
|
|||||||
public OAuth2AccessToken getAccessToken() {
|
public OAuth2AccessToken getAccessToken() {
|
||||||
return this.accessToken;
|
return this.accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link OAuth2RefreshToken refresh token}.
|
||||||
|
*
|
||||||
|
* @return the {@link OAuth2RefreshToken} or {@code null} if not available
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public OAuth2RefreshToken getRefreshToken() {
|
||||||
|
return this.refreshToken;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenMetadata;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for the OAuth 2.0 {@link AuthenticationProvider}'s.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.3
|
||||||
|
*/
|
||||||
|
final class OAuth2AuthenticationProviderUtils {
|
||||||
|
|
||||||
|
private OAuth2AuthenticationProviderUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
||||||
|
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
|
||||||
|
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
|
||||||
|
}
|
||||||
|
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
|
||||||
|
return clientPrincipal;
|
||||||
|
}
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends AbstractOAuth2Token> OAuth2Authorization invalidate(
|
||||||
|
OAuth2Authorization authorization, T token) {
|
||||||
|
|
||||||
|
OAuth2Tokens.Builder builder = OAuth2Tokens.from(authorization.getTokens())
|
||||||
|
.token(token, OAuth2TokenMetadata.builder().invalidated().build());
|
||||||
|
|
||||||
|
if (OAuth2RefreshToken.class.isAssignableFrom(token.getClass())) {
|
||||||
|
builder.token(
|
||||||
|
authorization.getTokens().getAccessToken(),
|
||||||
|
OAuth2TokenMetadata.builder().invalidated().build());
|
||||||
|
OAuth2AuthorizationCode authorizationCode =
|
||||||
|
authorization.getTokens().getToken(OAuth2AuthorizationCode.class);
|
||||||
|
if (authorizationCode != null &&
|
||||||
|
!authorization.getTokens().getTokenMetadata(authorizationCode).isInvalidated()) {
|
||||||
|
builder.token(
|
||||||
|
authorizationCode,
|
||||||
|
OAuth2TokenMetadata.builder().invalidated().build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return OAuth2Authorization.from(authorization)
|
||||||
|
.tokens(builder.build())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,32 +22,31 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
|||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
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.OAuth2RefreshToken;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
||||||
import org.springframework.security.oauth2.jose.JoseHeader;
|
|
||||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
|
||||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
import org.springframework.security.oauth2.server.authorization.TokenType;
|
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenMetadata;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
import java.util.Set;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
import static org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient;
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.temporal.ChronoUnit;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization Code Grant.
|
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization Code Grant.
|
||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
|
* @author Daniel Garnier-Moiroux
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
* @see OAuth2AuthorizationCodeAuthenticationToken
|
* @see OAuth2AuthorizationCodeAuthenticationToken
|
||||||
* @see OAuth2AccessTokenAuthenticationToken
|
* @see OAuth2AccessTokenAuthenticationToken
|
||||||
@@ -84,72 +83,66 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
|
|||||||
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
|
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
|
||||||
(OAuth2AuthorizationCodeAuthenticationToken) authentication;
|
(OAuth2AuthorizationCodeAuthenticationToken) authentication;
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
OAuth2ClientAuthenticationToken clientPrincipal =
|
||||||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authorizationCodeAuthentication.getPrincipal().getClass())) {
|
getAuthenticatedClientElseThrowInvalidClient(authorizationCodeAuthentication);
|
||||||
clientPrincipal = (OAuth2ClientAuthenticationToken) authorizationCodeAuthentication.getPrincipal();
|
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||||
}
|
|
||||||
if (clientPrincipal == null || !clientPrincipal.isAuthenticated()) {
|
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Authenticate public client
|
|
||||||
// A client MAY use the "client_id" request parameter to identify itself
|
|
||||||
// when sending requests to the token endpoint.
|
|
||||||
// In the "authorization_code" "grant_type" request to the token endpoint,
|
|
||||||
// an unauthenticated client MUST send its "client_id" to prevent itself
|
|
||||||
// from inadvertently accepting a code intended for a client with a different "client_id".
|
|
||||||
// This protects the client from substitution of the authentication code.
|
|
||||||
|
|
||||||
OAuth2Authorization authorization = this.authorizationService.findByToken(
|
OAuth2Authorization authorization = this.authorizationService.findByToken(
|
||||||
authorizationCodeAuthentication.getCode(), TokenType.AUTHORIZATION_CODE);
|
authorizationCodeAuthentication.getCode(), TokenType.AUTHORIZATION_CODE);
|
||||||
if (authorization == null) {
|
if (authorization == null) {
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
|
||||||
}
|
}
|
||||||
if (!clientPrincipal.getRegisteredClient().getId().equals(authorization.getRegisteredClientId())) {
|
OAuth2AuthorizationCode authorizationCode = authorization.getTokens().getToken(OAuth2AuthorizationCode.class);
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
|
OAuth2TokenMetadata authorizationCodeMetadata = authorization.getTokens().getTokenMetadata(authorizationCode);
|
||||||
}
|
|
||||||
|
|
||||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||||
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
|
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
|
||||||
|
|
||||||
|
if (!registeredClient.getClientId().equals(authorizationRequest.getClientId())) {
|
||||||
|
if (!authorizationCodeMetadata.isInvalidated()) {
|
||||||
|
// Invalidate the authorization code given that a different client is attempting to use it
|
||||||
|
authorization = OAuth2AuthenticationProviderUtils.invalidate(authorization, authorizationCode);
|
||||||
|
this.authorizationService.save(authorization);
|
||||||
|
}
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
|
||||||
|
}
|
||||||
|
|
||||||
if (StringUtils.hasText(authorizationRequest.getRedirectUri()) &&
|
if (StringUtils.hasText(authorizationRequest.getRedirectUri()) &&
|
||||||
!authorizationRequest.getRedirectUri().equals(authorizationCodeAuthentication.getRedirectUri())) {
|
!authorizationRequest.getRedirectUri().equals(authorizationCodeAuthentication.getRedirectUri())) {
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
|
||||||
}
|
}
|
||||||
|
|
||||||
JoseHeader joseHeader = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build();
|
if (authorizationCodeMetadata.isInvalidated()) {
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
|
||||||
// TODO Allow configuration for issuer claim
|
}
|
||||||
URL issuer = null;
|
|
||||||
try {
|
|
||||||
issuer = URI.create("https://oauth2.provider.com").toURL();
|
|
||||||
} catch (MalformedURLException e) { }
|
|
||||||
|
|
||||||
Instant issuedAt = Instant.now();
|
|
||||||
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); // TODO Allow configuration for access token time-to-live
|
|
||||||
|
|
||||||
JwtClaimsSet jwtClaimsSet = JwtClaimsSet.withClaims()
|
|
||||||
.issuer(issuer)
|
|
||||||
.subject(authorization.getPrincipalName())
|
|
||||||
.audience(Collections.singletonList(clientPrincipal.getRegisteredClient().getClientId()))
|
|
||||||
.issuedAt(issuedAt)
|
|
||||||
.expiresAt(expiresAt)
|
|
||||||
.notBefore(issuedAt)
|
|
||||||
.claim(OAuth2ParameterNames.SCOPE, authorizationRequest.getScopes())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Jwt jwt = this.jwtEncoder.encode(joseHeader, jwtClaimsSet);
|
|
||||||
|
|
||||||
|
Set<String> authorizedScopes = authorization.getAttribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES);
|
||||||
|
Jwt jwt = OAuth2TokenIssuerUtil
|
||||||
|
.issueJwtAccessToken(this.jwtEncoder, authorization.getPrincipalName(), registeredClient.getClientId(), authorizedScopes);
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||||
jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
|
jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), authorizedScopes);
|
||||||
|
|
||||||
|
OAuth2Tokens.Builder tokensBuilder = OAuth2Tokens.from(authorization.getTokens())
|
||||||
|
.accessToken(accessToken);
|
||||||
|
|
||||||
|
OAuth2RefreshToken refreshToken = null;
|
||||||
|
if (registeredClient.getTokenSettings().enableRefreshTokens()) {
|
||||||
|
refreshToken = OAuth2TokenIssuerUtil.issueRefreshToken(registeredClient.getTokenSettings().refreshTokenTimeToLive());
|
||||||
|
tokensBuilder.refreshToken(refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
OAuth2Tokens tokens = tokensBuilder.build();
|
||||||
authorization = OAuth2Authorization.from(authorization)
|
authorization = OAuth2Authorization.from(authorization)
|
||||||
|
.tokens(tokens)
|
||||||
.attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt)
|
.attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt)
|
||||||
.accessToken(accessToken)
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
// Invalidate the authorization code as it can only be used once
|
||||||
|
authorization = OAuth2AuthenticationProviderUtils.invalidate(authorization, authorizationCode);
|
||||||
|
|
||||||
this.authorizationService.save(authorization);
|
this.authorizationService.save(authorization);
|
||||||
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(
|
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken);
|
||||||
clientPrincipal.getRegisteredClient(), clientPrincipal, accessToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -18,27 +18,29 @@ package org.springframework.security.oauth2.server.authorization.authentication;
|
|||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.SpringSecurityCoreVersion2;
|
import org.springframework.security.oauth2.server.authorization.Version;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link Authentication} implementation used for the OAuth 2.0 Authorization Code Grant.
|
* An {@link Authentication} implementation used for the OAuth 2.0 Authorization Code Grant.
|
||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @author Madhu Bhat
|
* @author Madhu Bhat
|
||||||
|
* @author Daniel Garnier-Moiroux
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
* @see AbstractAuthenticationToken
|
* @see AbstractAuthenticationToken
|
||||||
* @see OAuth2AuthorizationCodeAuthenticationProvider
|
* @see OAuth2AuthorizationCodeAuthenticationProvider
|
||||||
* @see OAuth2ClientAuthenticationToken
|
* @see OAuth2ClientAuthenticationToken
|
||||||
*/
|
*/
|
||||||
public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenticationToken {
|
public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenticationToken {
|
||||||
private static final long serialVersionUID = SpringSecurityCoreVersion2.SERIAL_VERSION_UID;
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
private String code;
|
private final String code;
|
||||||
private Authentication clientPrincipal;
|
private final Authentication clientPrincipal;
|
||||||
private String clientId;
|
private final String redirectUri;
|
||||||
private String redirectUri;
|
private final Map<String, Object> additionalParameters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an {@code OAuth2AuthorizationCodeAuthenticationToken} using the provided parameters.
|
* Constructs an {@code OAuth2AuthorizationCodeAuthenticationToken} using the provided parameters.
|
||||||
@@ -46,37 +48,25 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
|
|||||||
* @param code the authorization code
|
* @param code the authorization code
|
||||||
* @param clientPrincipal the authenticated client principal
|
* @param clientPrincipal the authenticated client principal
|
||||||
* @param redirectUri the redirect uri
|
* @param redirectUri the redirect uri
|
||||||
|
* @param additionalParameters the additional parameters
|
||||||
*/
|
*/
|
||||||
public OAuth2AuthorizationCodeAuthenticationToken(String code,
|
public OAuth2AuthorizationCodeAuthenticationToken(String code, Authentication clientPrincipal,
|
||||||
Authentication clientPrincipal, @Nullable String redirectUri) {
|
@Nullable String redirectUri, @Nullable Map<String, Object> additionalParameters) {
|
||||||
super(Collections.emptyList());
|
super(Collections.emptyList());
|
||||||
Assert.hasText(code, "code cannot be empty");
|
Assert.hasText(code, "code cannot be empty");
|
||||||
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
|
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.clientPrincipal = clientPrincipal;
|
this.clientPrincipal = clientPrincipal;
|
||||||
this.redirectUri = redirectUri;
|
this.redirectUri = redirectUri;
|
||||||
}
|
this.additionalParameters = Collections.unmodifiableMap(
|
||||||
|
additionalParameters != null ?
|
||||||
/**
|
additionalParameters :
|
||||||
* Constructs an {@code OAuth2AuthorizationCodeAuthenticationToken} using the provided parameters.
|
Collections.emptyMap());
|
||||||
*
|
|
||||||
* @param code the authorization code
|
|
||||||
* @param clientId the client identifier
|
|
||||||
* @param redirectUri the redirect uri
|
|
||||||
*/
|
|
||||||
public OAuth2AuthorizationCodeAuthenticationToken(String code,
|
|
||||||
String clientId, @Nullable String redirectUri) {
|
|
||||||
super(Collections.emptyList());
|
|
||||||
Assert.hasText(code, "code cannot be empty");
|
|
||||||
Assert.hasText(clientId, "clientId cannot be empty");
|
|
||||||
this.code = code;
|
|
||||||
this.clientId = clientId;
|
|
||||||
this.redirectUri = redirectUri;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getPrincipal() {
|
public Object getPrincipal() {
|
||||||
return this.clientPrincipal != null ? this.clientPrincipal : this.clientId;
|
return this.clientPrincipal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -101,4 +91,13 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
|
|||||||
public @Nullable String getRedirectUri() {
|
public @Nullable String getRedirectUri() {
|
||||||
return this.redirectUri;
|
return this.redirectUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the additional parameters
|
||||||
|
*
|
||||||
|
* @return the additional parameters
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getAdditionalParameters() {
|
||||||
|
return this.additionalParameters;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,47 +18,85 @@ package org.springframework.security.oauth2.server.authorization.authentication;
|
|||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
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 org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
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.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link AuthenticationProvider} implementation that validates {@link OAuth2ClientAuthenticationToken}'s.
|
* An {@link AuthenticationProvider} implementation used for authenticating an OAuth 2.0 Client.
|
||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @author Patryk Kostrzewa
|
* @author Patryk Kostrzewa
|
||||||
|
* @author Daniel Garnier-Moiroux
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
* @see AuthenticationProvider
|
* @see AuthenticationProvider
|
||||||
* @see OAuth2ClientAuthenticationToken
|
* @see OAuth2ClientAuthenticationToken
|
||||||
* @see RegisteredClientRepository
|
* @see RegisteredClientRepository
|
||||||
|
* @see OAuth2AuthorizationService
|
||||||
*/
|
*/
|
||||||
public class OAuth2ClientAuthenticationProvider implements AuthenticationProvider {
|
public class OAuth2ClientAuthenticationProvider implements AuthenticationProvider {
|
||||||
private final RegisteredClientRepository registeredClientRepository;
|
private final RegisteredClientRepository registeredClientRepository;
|
||||||
|
private final OAuth2AuthorizationService authorizationService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an {@code OAuth2ClientAuthenticationProvider} using the provided parameters.
|
* Constructs an {@code OAuth2ClientAuthenticationProvider} using the provided parameters.
|
||||||
*
|
*
|
||||||
* @param registeredClientRepository the repository of registered clients
|
* @param registeredClientRepository the repository of registered clients
|
||||||
|
* @param authorizationService the authorization service
|
||||||
*/
|
*/
|
||||||
public OAuth2ClientAuthenticationProvider(RegisteredClientRepository registeredClientRepository) {
|
public OAuth2ClientAuthenticationProvider(RegisteredClientRepository registeredClientRepository,
|
||||||
|
OAuth2AuthorizationService authorizationService) {
|
||||||
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
|
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
|
||||||
|
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||||
this.registeredClientRepository = registeredClientRepository;
|
this.registeredClientRepository = registeredClientRepository;
|
||||||
|
this.authorizationService = authorizationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
String clientId = authentication.getPrincipal().toString();
|
OAuth2ClientAuthenticationToken clientAuthentication =
|
||||||
|
(OAuth2ClientAuthenticationToken) authentication;
|
||||||
|
|
||||||
|
String clientId = clientAuthentication.getPrincipal().toString();
|
||||||
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
|
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
|
||||||
if (registeredClient == null) {
|
if (registeredClient == null) {
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
|
throwInvalidClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
String clientSecret = authentication.getCredentials().toString();
|
boolean authenticatedCredentials = false;
|
||||||
|
|
||||||
|
if (clientAuthentication.getCredentials() != null) {
|
||||||
|
String clientSecret = clientAuthentication.getCredentials().toString();
|
||||||
|
// TODO Use PasswordEncoder.matches()
|
||||||
if (!registeredClient.getClientSecret().equals(clientSecret)) {
|
if (!registeredClient.getClientSecret().equals(clientSecret)) {
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
|
throwInvalidClient();
|
||||||
|
}
|
||||||
|
authenticatedCredentials = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticatedCredentials = authenticatedCredentials ||
|
||||||
|
authenticatePkceIfAvailable(clientAuthentication, registeredClient);
|
||||||
|
if (!authenticatedCredentials) {
|
||||||
|
throwInvalidClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OAuth2ClientAuthenticationToken(registeredClient);
|
return new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
@@ -68,4 +106,68 @@ public class OAuth2ClientAuthenticationProvider implements AuthenticationProvide
|
|||||||
public boolean supports(Class<?> authentication) {
|
public boolean supports(Class<?> authentication) {
|
||||||
return OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication);
|
return OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean authenticatePkceIfAvailable(OAuth2ClientAuthenticationToken clientAuthentication,
|
||||||
|
RegisteredClient registeredClient) {
|
||||||
|
|
||||||
|
Map<String, Object> parameters = clientAuthentication.getAdditionalParameters();
|
||||||
|
if (CollectionUtils.isEmpty(parameters) || !authorizationCodeGrant(parameters)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = this.authorizationService.findByToken(
|
||||||
|
(String) parameters.get(OAuth2ParameterNames.CODE),
|
||||||
|
TokenType.AUTHORIZATION_CODE);
|
||||||
|
if (authorization == null) {
|
||||||
|
throwInvalidClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||||
|
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
|
||||||
|
|
||||||
|
String codeChallenge = (String) authorizationRequest.getAdditionalParameters()
|
||||||
|
.get(PkceParameterNames.CODE_CHALLENGE);
|
||||||
|
if (!StringUtils.hasText(codeChallenge) &&
|
||||||
|
registeredClient.getClientSettings().requireProofKey()) {
|
||||||
|
throwInvalidClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
String codeChallengeMethod = (String) authorizationRequest.getAdditionalParameters()
|
||||||
|
.get(PkceParameterNames.CODE_CHALLENGE_METHOD);
|
||||||
|
String codeVerifier = (String) parameters.get(PkceParameterNames.CODE_VERIFIER);
|
||||||
|
if (!codeVerifierValid(codeVerifier, codeChallenge, codeChallengeMethod)) {
|
||||||
|
throwInvalidClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean authorizationCodeGrant(Map<String, Object> parameters) {
|
||||||
|
return AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(
|
||||||
|
parameters.get(OAuth2ParameterNames.GRANT_TYPE)) &&
|
||||||
|
parameters.get(OAuth2ParameterNames.CODE) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean codeVerifierValid(String codeVerifier, String codeChallenge, String codeChallengeMethod) {
|
||||||
|
if (!StringUtils.hasText(codeVerifier)) {
|
||||||
|
return false;
|
||||||
|
} else if (!StringUtils.hasText(codeChallengeMethod) || "plain".equals(codeChallengeMethod)) {
|
||||||
|
return codeVerifier.equals(codeChallenge);
|
||||||
|
} else if ("S256".equals(codeChallengeMethod)) {
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] digest = md.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
|
||||||
|
String encodedVerifier = Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
|
||||||
|
return encodedVerifier.equals(codeChallenge);
|
||||||
|
} catch (NoSuchAlgorithmException ex) {
|
||||||
|
// It is unlikely that SHA-256 is not available on the server. If it is not available,
|
||||||
|
// there will likely be bigger issues as well. We default to SERVER_ERROR.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void throwInvalidClient() {
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ package org.springframework.security.oauth2.server.authorization.authentication;
|
|||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.SpringSecurityCoreVersion2;
|
import org.springframework.security.oauth2.server.authorization.Version;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link Authentication} implementation used for OAuth 2.0 Client Authentication.
|
* An {@link Authentication} implementation used for OAuth 2.0 Client Authentication.
|
||||||
@@ -35,9 +36,10 @@ import java.util.Collections;
|
|||||||
* @see OAuth2ClientAuthenticationProvider
|
* @see OAuth2ClientAuthenticationProvider
|
||||||
*/
|
*/
|
||||||
public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken {
|
public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken {
|
||||||
private static final long serialVersionUID = SpringSecurityCoreVersion2.SERIAL_VERSION_UID;
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
private String clientId;
|
private String clientId;
|
||||||
private String clientSecret;
|
private String clientSecret;
|
||||||
|
private Map<String, Object> additionalParameters;
|
||||||
private RegisteredClient registeredClient;
|
private RegisteredClient registeredClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,13 +47,28 @@ public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken
|
|||||||
*
|
*
|
||||||
* @param clientId the client identifier
|
* @param clientId the client identifier
|
||||||
* @param clientSecret the client secret
|
* @param clientSecret the client secret
|
||||||
|
* @param additionalParameters the additional parameters
|
||||||
*/
|
*/
|
||||||
public OAuth2ClientAuthenticationToken(String clientId, String clientSecret) {
|
public OAuth2ClientAuthenticationToken(String clientId, String clientSecret,
|
||||||
|
@Nullable Map<String, Object> additionalParameters) {
|
||||||
|
this(clientId, additionalParameters);
|
||||||
|
Assert.hasText(clientSecret, "clientSecret cannot be empty");
|
||||||
|
this.clientSecret = clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2ClientAuthenticationToken} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param clientId the client identifier
|
||||||
|
* @param additionalParameters the additional parameters
|
||||||
|
*/
|
||||||
|
public OAuth2ClientAuthenticationToken(String clientId,
|
||||||
|
@Nullable Map<String, Object> additionalParameters) {
|
||||||
super(Collections.emptyList());
|
super(Collections.emptyList());
|
||||||
Assert.hasText(clientId, "clientId cannot be empty");
|
Assert.hasText(clientId, "clientId cannot be empty");
|
||||||
Assert.hasText(clientSecret, "clientSecret cannot be empty");
|
|
||||||
this.clientId = clientId;
|
this.clientId = clientId;
|
||||||
this.clientSecret = clientSecret;
|
this.additionalParameters = additionalParameters != null ?
|
||||||
|
Collections.unmodifiableMap(additionalParameters) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,6 +95,15 @@ public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken
|
|||||||
return this.clientSecret;
|
return this.clientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the additional parameters
|
||||||
|
*
|
||||||
|
* @return the additional parameters
|
||||||
|
*/
|
||||||
|
public @Nullable Map<String, Object> getAdditionalParameters() {
|
||||||
|
return this.additionalParameters;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link RegisteredClient registered client}.
|
* Returns the {@link RegisteredClient registered client}.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -18,33 +18,27 @@ package org.springframework.security.oauth2.server.authorization.authentication;
|
|||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
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 org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
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.endpoint.OAuth2ParameterNames;
|
|
||||||
import org.springframework.security.oauth2.jose.JoseHeader;
|
|
||||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
|
||||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.temporal.ChronoUnit;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Client Credentials Grant.
|
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Client Credentials Grant.
|
||||||
*
|
*
|
||||||
@@ -80,15 +74,14 @@ public class OAuth2ClientCredentialsAuthenticationProvider implements Authentica
|
|||||||
OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication =
|
OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication =
|
||||||
(OAuth2ClientCredentialsAuthenticationToken) authentication;
|
(OAuth2ClientCredentialsAuthenticationToken) authentication;
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
OAuth2ClientAuthenticationToken clientPrincipal =
|
||||||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(clientCredentialsAuthentication.getPrincipal().getClass())) {
|
getAuthenticatedClientElseThrowInvalidClient(clientCredentialsAuthentication);
|
||||||
clientPrincipal = (OAuth2ClientAuthenticationToken) clientCredentialsAuthentication.getPrincipal();
|
|
||||||
}
|
|
||||||
if (clientPrincipal == null || !clientPrincipal.isAuthenticated()) {
|
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
|
|
||||||
}
|
|
||||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||||
|
|
||||||
|
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.CLIENT_CREDENTIALS)) {
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT));
|
||||||
|
}
|
||||||
|
|
||||||
Set<String> scopes = registeredClient.getScopes(); // Default to configured scopes
|
Set<String> scopes = registeredClient.getScopes(); // Default to configured scopes
|
||||||
if (!CollectionUtils.isEmpty(clientCredentialsAuthentication.getScopes())) {
|
if (!CollectionUtils.isEmpty(clientCredentialsAuthentication.getScopes())) {
|
||||||
Set<String> unauthorizedScopes = clientCredentialsAuthentication.getScopes().stream()
|
Set<String> unauthorizedScopes = clientCredentialsAuthentication.getScopes().stream()
|
||||||
@@ -100,36 +93,15 @@ public class OAuth2ClientCredentialsAuthenticationProvider implements Authentica
|
|||||||
scopes = new LinkedHashSet<>(clientCredentialsAuthentication.getScopes());
|
scopes = new LinkedHashSet<>(clientCredentialsAuthentication.getScopes());
|
||||||
}
|
}
|
||||||
|
|
||||||
JoseHeader joseHeader = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build();
|
Jwt jwt = OAuth2TokenIssuerUtil
|
||||||
|
.issueJwtAccessToken(this.jwtEncoder, clientPrincipal.getName(), registeredClient.getClientId(), scopes);
|
||||||
// TODO Allow configuration for issuer claim
|
|
||||||
URL issuer = null;
|
|
||||||
try {
|
|
||||||
issuer = URI.create("https://oauth2.provider.com").toURL();
|
|
||||||
} catch (MalformedURLException e) { }
|
|
||||||
|
|
||||||
Instant issuedAt = Instant.now();
|
|
||||||
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); // TODO Allow configuration for access token time-to-live
|
|
||||||
|
|
||||||
JwtClaimsSet jwtClaimsSet = JwtClaimsSet.withClaims()
|
|
||||||
.issuer(issuer)
|
|
||||||
.subject(clientPrincipal.getName())
|
|
||||||
.audience(Collections.singletonList(registeredClient.getClientId()))
|
|
||||||
.issuedAt(issuedAt)
|
|
||||||
.expiresAt(expiresAt)
|
|
||||||
.notBefore(issuedAt)
|
|
||||||
.claim(OAuth2ParameterNames.SCOPE, scopes)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Jwt jwt = this.jwtEncoder.encode(joseHeader, jwtClaimsSet);
|
|
||||||
|
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||||
jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), scopes);
|
jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), scopes);
|
||||||
|
|
||||||
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
|
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||||
.attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt)
|
|
||||||
.principalName(clientPrincipal.getName())
|
.principalName(clientPrincipal.getName())
|
||||||
.accessToken(accessToken)
|
.tokens(OAuth2Tokens.builder().accessToken(accessToken).build())
|
||||||
|
.attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt)
|
||||||
.build();
|
.build();
|
||||||
this.authorizationService.save(authorization);
|
this.authorizationService.save(authorization);
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ package org.springframework.security.oauth2.server.authorization.authentication;
|
|||||||
|
|
||||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.SpringSecurityCoreVersion2;
|
import org.springframework.security.oauth2.server.authorization.Version;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -34,7 +34,7 @@ import java.util.Set;
|
|||||||
* @see OAuth2ClientAuthenticationToken
|
* @see OAuth2ClientAuthenticationToken
|
||||||
*/
|
*/
|
||||||
public class OAuth2ClientCredentialsAuthenticationToken extends AbstractAuthenticationToken {
|
public class OAuth2ClientCredentialsAuthenticationToken extends AbstractAuthenticationToken {
|
||||||
private static final long serialVersionUID = SpringSecurityCoreVersion2.SERIAL_VERSION_UID;
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
private final Authentication clientPrincipal;
|
private final Authentication clientPrincipal;
|
||||||
private final Set<String> scopes;
|
private final Set<String> scopes;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Refresh Token Grant.
|
||||||
|
*
|
||||||
|
* @author Alexey Nesterov
|
||||||
|
* @since 0.0.3
|
||||||
|
* @see OAuth2RefreshTokenAuthenticationToken
|
||||||
|
* @see OAuth2AccessTokenAuthenticationToken
|
||||||
|
* @see OAuth2AuthorizationService
|
||||||
|
* @see JwtEncoder
|
||||||
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.5">Section 1.5 Refresh Token Grant</a>
|
||||||
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-6">Section 6 Refreshing an Access Token</a>
|
||||||
|
*/
|
||||||
|
public class OAuth2RefreshTokenAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
private final OAuth2AuthorizationService authorizationService;
|
||||||
|
private final JwtEncoder jwtEncoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2RefreshTokenAuthenticationProvider} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param authorizationService the authorization service
|
||||||
|
* @param jwtEncoder the jwt encoder
|
||||||
|
*/
|
||||||
|
public OAuth2RefreshTokenAuthenticationProvider(OAuth2AuthorizationService authorizationService,
|
||||||
|
JwtEncoder jwtEncoder) {
|
||||||
|
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||||
|
Assert.notNull(jwtEncoder, "jwtEncoder cannot be null");
|
||||||
|
this.authorizationService = authorizationService;
|
||||||
|
this.jwtEncoder = jwtEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
|
OAuth2RefreshTokenAuthenticationToken refreshTokenAuthentication =
|
||||||
|
(OAuth2RefreshTokenAuthenticationToken) authentication;
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal =
|
||||||
|
getAuthenticatedClientElseThrowInvalidClient(refreshTokenAuthentication);
|
||||||
|
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = this.authorizationService.findByToken(
|
||||||
|
refreshTokenAuthentication.getRefreshToken(), TokenType.REFRESH_TOKEN);
|
||||||
|
if (authorization == null) {
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) {
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) {
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT));
|
||||||
|
}
|
||||||
|
|
||||||
|
Instant refreshTokenExpiresAt = authorization.getTokens().getRefreshToken().getExpiresAt();
|
||||||
|
if (refreshTokenExpiresAt.isBefore(Instant.now())) {
|
||||||
|
// As per https://tools.ietf.org/html/rfc6749#section-5.2
|
||||||
|
// invalid_grant: The provided authorization grant (e.g., authorization code,
|
||||||
|
// resource owner credentials) or refresh token is invalid, expired, revoked [...].
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
|
||||||
|
}
|
||||||
|
|
||||||
|
// As per https://tools.ietf.org/html/rfc6749#section-6
|
||||||
|
// The requested scope MUST NOT include any scope not originally granted by the resource owner,
|
||||||
|
// and if omitted is treated as equal to the scope originally granted by the resource owner.
|
||||||
|
Set<String> scopes = refreshTokenAuthentication.getScopes();
|
||||||
|
Set<String> authorizedScopes = authorization.getAttribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES);
|
||||||
|
if (!authorizedScopes.containsAll(scopes)) {
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE));
|
||||||
|
}
|
||||||
|
if (scopes.isEmpty()) {
|
||||||
|
scopes = authorizedScopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Jwt jwt = OAuth2TokenIssuerUtil
|
||||||
|
.issueJwtAccessToken(this.jwtEncoder, authorization.getPrincipalName(), registeredClient.getClientId(), scopes);
|
||||||
|
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||||
|
jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), scopes);
|
||||||
|
|
||||||
|
TokenSettings tokenSettings = registeredClient.getTokenSettings();
|
||||||
|
OAuth2RefreshToken refreshToken;
|
||||||
|
if (tokenSettings.reuseRefreshTokens()) {
|
||||||
|
refreshToken = authorization.getTokens().getRefreshToken();
|
||||||
|
} else {
|
||||||
|
refreshToken = OAuth2TokenIssuerUtil.issueRefreshToken(tokenSettings.refreshTokenTimeToLive());
|
||||||
|
}
|
||||||
|
|
||||||
|
authorization = OAuth2Authorization.from(authorization)
|
||||||
|
.tokens(OAuth2Tokens.from(authorization.getTokens()).accessToken(accessToken).refreshToken(refreshToken).build())
|
||||||
|
.attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt)
|
||||||
|
.build();
|
||||||
|
this.authorizationService.save(authorization);
|
||||||
|
|
||||||
|
return new OAuth2AccessTokenAuthenticationToken(
|
||||||
|
registeredClient, clientPrincipal, accessToken, refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> authentication) {
|
||||||
|
return OAuth2RefreshTokenAuthenticationToken.class.isAssignableFrom(authentication);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.Version;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link Authentication} implementation used for the OAuth 2.0 Refresh Token Grant.
|
||||||
|
*
|
||||||
|
* @author Alexey Nesterov
|
||||||
|
* @since 0.0.3
|
||||||
|
* @see AbstractAuthenticationToken
|
||||||
|
* @see OAuth2RefreshTokenAuthenticationProvider
|
||||||
|
* @see OAuth2ClientAuthenticationToken
|
||||||
|
*/
|
||||||
|
public class OAuth2RefreshTokenAuthenticationToken extends AbstractAuthenticationToken {
|
||||||
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
|
private final String refreshToken;
|
||||||
|
private final Authentication clientPrincipal;
|
||||||
|
private final Set<String> scopes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2RefreshTokenAuthenticationToken} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param refreshToken the refresh token
|
||||||
|
* @param clientPrincipal the authenticated client principal
|
||||||
|
*/
|
||||||
|
public OAuth2RefreshTokenAuthenticationToken(String refreshToken, Authentication clientPrincipal) {
|
||||||
|
this(refreshToken, clientPrincipal, Collections.emptySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2RefreshTokenAuthenticationToken} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param refreshToken the refresh token
|
||||||
|
* @param clientPrincipal the authenticated client principal
|
||||||
|
* @param scopes the requested scope(s)
|
||||||
|
*/
|
||||||
|
public OAuth2RefreshTokenAuthenticationToken(String refreshToken, Authentication clientPrincipal,
|
||||||
|
Set<String> scopes) {
|
||||||
|
super(Collections.emptySet());
|
||||||
|
Assert.hasText(refreshToken, "refreshToken cannot be empty");
|
||||||
|
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
|
||||||
|
Assert.notNull(scopes, "scopes cannot be null");
|
||||||
|
this.refreshToken = refreshToken;
|
||||||
|
this.clientPrincipal = clientPrincipal;
|
||||||
|
this.scopes = scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return this.clientPrincipal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCredentials() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the refresh token.
|
||||||
|
*
|
||||||
|
* @return the refresh token
|
||||||
|
*/
|
||||||
|
public String getRefreshToken() {
|
||||||
|
return this.refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the requested scope(s).
|
||||||
|
*
|
||||||
|
* @return the requested scope(s), or an empty {@code Set} if not available
|
||||||
|
*/
|
||||||
|
public Set<String> getScopes() {
|
||||||
|
return this.scopes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||||
|
|
||||||
|
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
||||||
|
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.jose.JoseHeader;
|
||||||
|
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Alexey Nesterov
|
||||||
|
* @since 0.0.3
|
||||||
|
*/
|
||||||
|
class OAuth2TokenIssuerUtil {
|
||||||
|
|
||||||
|
private static final StringKeyGenerator TOKEN_GENERATOR = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
|
||||||
|
|
||||||
|
static Jwt issueJwtAccessToken(JwtEncoder jwtEncoder, String subject, String audience, Set<String> scopes) {
|
||||||
|
JoseHeader joseHeader = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build();
|
||||||
|
|
||||||
|
// TODO Allow configuration for issuer claim
|
||||||
|
URL issuer = null;
|
||||||
|
try {
|
||||||
|
issuer = URI.create("https://oauth2.provider.com").toURL();
|
||||||
|
} catch (MalformedURLException e) { }
|
||||||
|
|
||||||
|
Instant issuedAt = Instant.now();
|
||||||
|
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); // TODO Allow configuration for access token time-to-live
|
||||||
|
|
||||||
|
JwtClaimsSet jwtClaimsSet = JwtClaimsSet.withClaims()
|
||||||
|
.issuer(issuer)
|
||||||
|
.subject(subject)
|
||||||
|
.audience(Collections.singletonList(audience))
|
||||||
|
.issuedAt(issuedAt)
|
||||||
|
.expiresAt(expiresAt)
|
||||||
|
.notBefore(issuedAt)
|
||||||
|
.claim(OAuth2ParameterNames.SCOPE, scopes)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return jwtEncoder.encode(joseHeader, jwtClaimsSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
static OAuth2RefreshToken issueRefreshToken(Duration refreshTokenTimeToLive) {
|
||||||
|
Instant issuedAt = Instant.now();
|
||||||
|
Instant expiresAt = issuedAt.plus(refreshTokenTimeToLive);
|
||||||
|
|
||||||
|
return new OAuth2RefreshToken2(TOKEN_GENERATOR.generateKey(), issuedAt, expiresAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes2;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import static org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AuthenticationProvider} implementation for OAuth 2.0 Token Revocation.
|
||||||
|
*
|
||||||
|
* @author Vivek Babu
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.3
|
||||||
|
* @see OAuth2TokenRevocationAuthenticationToken
|
||||||
|
* @see OAuth2AuthorizationService
|
||||||
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7009#section-2.1">Section 2.1 Revocation Request</a>
|
||||||
|
*/
|
||||||
|
public class OAuth2TokenRevocationAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
private final OAuth2AuthorizationService authorizationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2TokenRevocationAuthenticationProvider} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param authorizationService the authorization service
|
||||||
|
*/
|
||||||
|
public OAuth2TokenRevocationAuthenticationProvider(OAuth2AuthorizationService authorizationService) {
|
||||||
|
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||||
|
this.authorizationService = authorizationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
|
OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthentication =
|
||||||
|
(OAuth2TokenRevocationAuthenticationToken) authentication;
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal =
|
||||||
|
getAuthenticatedClientElseThrowInvalidClient(tokenRevocationAuthentication);
|
||||||
|
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||||
|
|
||||||
|
TokenType tokenType = null;
|
||||||
|
String tokenTypeHint = tokenRevocationAuthentication.getTokenTypeHint();
|
||||||
|
if (StringUtils.hasText(tokenTypeHint)) {
|
||||||
|
if (TokenType.REFRESH_TOKEN.getValue().equals(tokenTypeHint)) {
|
||||||
|
tokenType = TokenType.REFRESH_TOKEN;
|
||||||
|
} else if (TokenType.ACCESS_TOKEN.getValue().equals(tokenTypeHint)) {
|
||||||
|
tokenType = TokenType.ACCESS_TOKEN;
|
||||||
|
} else {
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes2.UNSUPPORTED_TOKEN_TYPE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = this.authorizationService.findByToken(
|
||||||
|
tokenRevocationAuthentication.getToken(), tokenType);
|
||||||
|
if (authorization == null) {
|
||||||
|
// Return the authentication request when token not found
|
||||||
|
return tokenRevocationAuthentication;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) {
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractOAuth2Token token = authorization.getTokens().getToken(tokenRevocationAuthentication.getToken());
|
||||||
|
authorization = OAuth2AuthenticationProviderUtils.invalidate(authorization, token);
|
||||||
|
this.authorizationService.save(authorization);
|
||||||
|
|
||||||
|
return new OAuth2TokenRevocationAuthenticationToken(token, clientPrincipal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> authentication) {
|
||||||
|
return OAuth2TokenRevocationAuthenticationToken.class.isAssignableFrom(authentication);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.Version;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link Authentication} implementation used for OAuth 2.0 Token Revocation.
|
||||||
|
*
|
||||||
|
* @author Vivek Babu
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.3
|
||||||
|
* @see AbstractAuthenticationToken
|
||||||
|
* @see OAuth2TokenRevocationAuthenticationProvider
|
||||||
|
*/
|
||||||
|
public class OAuth2TokenRevocationAuthenticationToken extends AbstractAuthenticationToken {
|
||||||
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
|
private final String token;
|
||||||
|
private final Authentication clientPrincipal;
|
||||||
|
private final String tokenTypeHint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2TokenRevocationAuthenticationToken} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param token the token
|
||||||
|
* @param clientPrincipal the authenticated client principal
|
||||||
|
* @param tokenTypeHint the token type hint
|
||||||
|
*/
|
||||||
|
public OAuth2TokenRevocationAuthenticationToken(String token,
|
||||||
|
Authentication clientPrincipal, @Nullable String tokenTypeHint) {
|
||||||
|
super(Collections.emptyList());
|
||||||
|
Assert.hasText(token, "token cannot be empty");
|
||||||
|
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
|
||||||
|
this.token = token;
|
||||||
|
this.clientPrincipal = clientPrincipal;
|
||||||
|
this.tokenTypeHint = tokenTypeHint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2TokenRevocationAuthenticationToken} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param revokedToken the revoked token
|
||||||
|
* @param clientPrincipal the authenticated client principal
|
||||||
|
*/
|
||||||
|
public OAuth2TokenRevocationAuthenticationToken(AbstractOAuth2Token revokedToken,
|
||||||
|
Authentication clientPrincipal) {
|
||||||
|
super(Collections.emptyList());
|
||||||
|
Assert.notNull(revokedToken, "revokedToken cannot be null");
|
||||||
|
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
|
||||||
|
this.token = revokedToken.getTokenValue();
|
||||||
|
this.clientPrincipal = clientPrincipal;
|
||||||
|
this.tokenTypeHint = null;
|
||||||
|
setAuthenticated(true); // Indicates that the token was authenticated and revoked
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return this.clientPrincipal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCredentials() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the token.
|
||||||
|
*
|
||||||
|
* @return the token
|
||||||
|
*/
|
||||||
|
public String getToken() {
|
||||||
|
return this.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the token type hint.
|
||||||
|
*
|
||||||
|
* @return the token type hint
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getTokenTypeHint() {
|
||||||
|
return this.tokenTypeHint;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
/**
|
/**
|
||||||
* A {@link RegisteredClientRepository} that stores {@link RegisteredClient}(s) in-memory.
|
* A {@link RegisteredClientRepository} that stores {@link RegisteredClient}(s) in-memory.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* <b>NOTE:</b> This implementation is recommended ONLY to be used during development/testing.
|
||||||
|
*
|
||||||
* @author Anoop Garlapati
|
* @author Anoop Garlapati
|
||||||
* @see RegisteredClientRepository
|
* @see RegisteredClientRepository
|
||||||
* @see RegisteredClient
|
* @see RegisteredClient
|
||||||
|
|||||||
@@ -15,9 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.server.authorization.client;
|
package org.springframework.security.oauth2.server.authorization.client;
|
||||||
|
|
||||||
import org.springframework.security.core.SpringSecurityCoreVersion2;
|
import org.springframework.security.oauth2.server.authorization.Version;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ import java.util.function.Consumer;
|
|||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
public class RegisteredClient implements Serializable {
|
public class RegisteredClient implements Serializable {
|
||||||
private static final long serialVersionUID = SpringSecurityCoreVersion2.SERIAL_VERSION_UID;
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
private String id;
|
private String id;
|
||||||
private String clientId;
|
private String clientId;
|
||||||
private String clientSecret;
|
private String clientSecret;
|
||||||
@@ -46,6 +48,8 @@ public class RegisteredClient implements Serializable {
|
|||||||
private Set<AuthorizationGrantType> authorizationGrantTypes;
|
private Set<AuthorizationGrantType> authorizationGrantTypes;
|
||||||
private Set<String> redirectUris;
|
private Set<String> redirectUris;
|
||||||
private Set<String> scopes;
|
private Set<String> scopes;
|
||||||
|
private ClientSettings clientSettings;
|
||||||
|
private TokenSettings tokenSettings;
|
||||||
|
|
||||||
protected RegisteredClient() {
|
protected RegisteredClient() {
|
||||||
}
|
}
|
||||||
@@ -114,6 +118,24 @@ public class RegisteredClient implements Serializable {
|
|||||||
return this.scopes;
|
return this.scopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link ClientSettings client configuration settings}.
|
||||||
|
*
|
||||||
|
* @return the {@link ClientSettings}
|
||||||
|
*/
|
||||||
|
public ClientSettings getClientSettings() {
|
||||||
|
return this.clientSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link TokenSettings token configuration settings}.
|
||||||
|
*
|
||||||
|
* @return the {@link TokenSettings}
|
||||||
|
*/
|
||||||
|
public TokenSettings getTokenSettings() {
|
||||||
|
return this.tokenSettings;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "RegisteredClient{" +
|
return "RegisteredClient{" +
|
||||||
@@ -152,7 +174,7 @@ public class RegisteredClient implements Serializable {
|
|||||||
* A builder for {@link RegisteredClient}.
|
* A builder for {@link RegisteredClient}.
|
||||||
*/
|
*/
|
||||||
public static class Builder implements Serializable {
|
public static class Builder implements Serializable {
|
||||||
private static final long serialVersionUID = SpringSecurityCoreVersion2.SERIAL_VERSION_UID;
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
private String id;
|
private String id;
|
||||||
private String clientId;
|
private String clientId;
|
||||||
private String clientSecret;
|
private String clientSecret;
|
||||||
@@ -160,6 +182,8 @@ public class RegisteredClient implements Serializable {
|
|||||||
private Set<AuthorizationGrantType> authorizationGrantTypes = new LinkedHashSet<>();
|
private Set<AuthorizationGrantType> authorizationGrantTypes = new LinkedHashSet<>();
|
||||||
private Set<String> redirectUris = new LinkedHashSet<>();
|
private Set<String> redirectUris = new LinkedHashSet<>();
|
||||||
private Set<String> scopes = new LinkedHashSet<>();
|
private Set<String> scopes = new LinkedHashSet<>();
|
||||||
|
private ClientSettings clientSettings = new ClientSettings();
|
||||||
|
private TokenSettings tokenSettings = new TokenSettings();
|
||||||
|
|
||||||
protected Builder(String id) {
|
protected Builder(String id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@@ -181,6 +205,8 @@ public class RegisteredClient implements Serializable {
|
|||||||
if (!CollectionUtils.isEmpty(registeredClient.scopes)) {
|
if (!CollectionUtils.isEmpty(registeredClient.scopes)) {
|
||||||
this.scopes.addAll(registeredClient.scopes);
|
this.scopes.addAll(registeredClient.scopes);
|
||||||
}
|
}
|
||||||
|
this.clientSettings = new ClientSettings(registeredClient.clientSettings.settings());
|
||||||
|
this.tokenSettings = new TokenSettings(registeredClient.tokenSettings.settings());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -310,6 +336,30 @@ public class RegisteredClient implements Serializable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Consumer} of the client configuration settings,
|
||||||
|
* allowing the ability to add, replace, or remove.
|
||||||
|
*
|
||||||
|
* @param clientSettingsConsumer a {@link Consumer} of the client configuration settings
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder clientSettings(Consumer<ClientSettings> clientSettingsConsumer) {
|
||||||
|
clientSettingsConsumer.accept(this.clientSettings);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Consumer} of the token configuration settings,
|
||||||
|
* allowing the ability to add, replace, or remove.
|
||||||
|
*
|
||||||
|
* @param tokenSettingsConsumer a {@link Consumer} of the token configuration settings
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder tokenSettings(Consumer<TokenSettings> tokenSettingsConsumer) {
|
||||||
|
tokenSettingsConsumer.accept(this.tokenSettings);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a new {@link RegisteredClient}.
|
* Builds a new {@link RegisteredClient}.
|
||||||
*
|
*
|
||||||
@@ -319,7 +369,6 @@ public class RegisteredClient implements Serializable {
|
|||||||
Assert.hasText(this.clientId, "clientId cannot be empty");
|
Assert.hasText(this.clientId, "clientId cannot be empty");
|
||||||
Assert.notEmpty(this.authorizationGrantTypes, "authorizationGrantTypes cannot be empty");
|
Assert.notEmpty(this.authorizationGrantTypes, "authorizationGrantTypes cannot be empty");
|
||||||
if (this.authorizationGrantTypes.contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
|
if (this.authorizationGrantTypes.contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
|
||||||
Assert.hasText(this.clientSecret, "clientSecret cannot be empty");
|
|
||||||
Assert.notEmpty(this.redirectUris, "redirectUris cannot be empty");
|
Assert.notEmpty(this.redirectUris, "redirectUris cannot be empty");
|
||||||
}
|
}
|
||||||
if (CollectionUtils.isEmpty(this.clientAuthenticationMethods)) {
|
if (CollectionUtils.isEmpty(this.clientAuthenticationMethods)) {
|
||||||
@@ -341,6 +390,8 @@ public class RegisteredClient implements Serializable {
|
|||||||
registeredClient.authorizationGrantTypes = Collections.unmodifiableSet(this.authorizationGrantTypes);
|
registeredClient.authorizationGrantTypes = Collections.unmodifiableSet(this.authorizationGrantTypes);
|
||||||
registeredClient.redirectUris = Collections.unmodifiableSet(this.redirectUris);
|
registeredClient.redirectUris = Collections.unmodifiableSet(this.redirectUris);
|
||||||
registeredClient.scopes = Collections.unmodifiableSet(this.scopes);
|
registeredClient.scopes = Collections.unmodifiableSet(this.scopes);
|
||||||
|
registeredClient.clientSettings = this.clientSettings;
|
||||||
|
registeredClient.tokenSettings = this.tokenSettings;
|
||||||
|
|
||||||
return registeredClient;
|
return registeredClient;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.config;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A facility for client configuration settings.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.2
|
||||||
|
* @see Settings
|
||||||
|
*/
|
||||||
|
public class ClientSettings extends Settings {
|
||||||
|
private static final String CLIENT_SETTING_BASE = "setting.client.";
|
||||||
|
public static final String REQUIRE_PROOF_KEY = CLIENT_SETTING_BASE.concat("require-proof-key");
|
||||||
|
public static final String REQUIRE_USER_CONSENT = CLIENT_SETTING_BASE.concat("require-user-consent");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code ClientSettings}.
|
||||||
|
*/
|
||||||
|
public ClientSettings() {
|
||||||
|
this(defaultSettings());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code ClientSettings} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param settings the initial settings
|
||||||
|
*/
|
||||||
|
public ClientSettings(Map<String, Object> settings) {
|
||||||
|
super(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the client is required to provide a proof key challenge and verifier
|
||||||
|
* when performing the Authorization Code Grant flow. The default is {@code false}.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the client is required to provide a proof key challenge and verifier, {@code false} otherwise
|
||||||
|
*/
|
||||||
|
public boolean requireProofKey() {
|
||||||
|
return setting(REQUIRE_PROOF_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to {@code true} if the client is required to provide a proof key challenge and verifier
|
||||||
|
* when performing the Authorization Code Grant flow.
|
||||||
|
*
|
||||||
|
* @param requireProofKey {@code true} if the client is required to provide a proof key challenge and verifier, {@code false} otherwise
|
||||||
|
* @return the {@link ClientSettings}
|
||||||
|
*/
|
||||||
|
public ClientSettings requireProofKey(boolean requireProofKey) {
|
||||||
|
setting(REQUIRE_PROOF_KEY, requireProofKey);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the user's consent is required when the client requests access.
|
||||||
|
* The default is {@code false}.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the user's consent is required when the client requests access, {@code false} otherwise
|
||||||
|
*/
|
||||||
|
public boolean requireUserConsent() {
|
||||||
|
return setting(REQUIRE_USER_CONSENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to {@code true} if the user's consent is required when the client requests access.
|
||||||
|
* This applies to all interactive flows (e.g. {@code authorization_code} and {@code device_code}).
|
||||||
|
*
|
||||||
|
* @param requireUserConsent {@code true} if the user's consent is required when the client requests access, {@code false} otherwise
|
||||||
|
* @return the {@link ClientSettings}
|
||||||
|
*/
|
||||||
|
public ClientSettings requireUserConsent(boolean requireUserConsent) {
|
||||||
|
setting(REQUIRE_USER_CONSENT, requireUserConsent);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Map<String, Object> defaultSettings() {
|
||||||
|
Map<String, Object> settings = new HashMap<>();
|
||||||
|
settings.put(REQUIRE_PROOF_KEY, false);
|
||||||
|
settings.put(REQUIRE_USER_CONSENT, false);
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.config;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.server.authorization.Version;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A facility for configuration settings.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.2
|
||||||
|
*/
|
||||||
|
public class Settings implements Serializable {
|
||||||
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
|
private final Map<String, Object> settings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code Settings}.
|
||||||
|
*/
|
||||||
|
public Settings() {
|
||||||
|
this.settings = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code Settings} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param settings the initial settings
|
||||||
|
*/
|
||||||
|
public Settings(Map<String, Object> settings) {
|
||||||
|
Assert.notNull(settings, "settings cannot be null");
|
||||||
|
this.settings = new HashMap<>(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a configuration setting.
|
||||||
|
*
|
||||||
|
* @param name the name of the setting
|
||||||
|
* @param <T> the type of the setting
|
||||||
|
* @return the value of the setting, or {@code null} if not available
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T setting(String name) {
|
||||||
|
Assert.hasText(name, "name cannot be empty");
|
||||||
|
return (T) this.settings.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a configuration setting.
|
||||||
|
*
|
||||||
|
* @param name the name of the setting
|
||||||
|
* @param value the value of the setting
|
||||||
|
* @param <T> the type of the {@link Settings}
|
||||||
|
* @return the {@link Settings}
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends Settings> T setting(String name, Object value) {
|
||||||
|
Assert.hasText(name, "name cannot be empty");
|
||||||
|
Assert.notNull(value, "value cannot be null");
|
||||||
|
this.settings.put(name, value);
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@code Map} of the configuration settings.
|
||||||
|
*
|
||||||
|
* @return a {@code Map} of the configuration settings
|
||||||
|
*/
|
||||||
|
public Map<String, Object> settings() {
|
||||||
|
return this.settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code Consumer} of the configuration settings {@code Map}
|
||||||
|
* allowing the ability to add, replace, or remove.
|
||||||
|
*
|
||||||
|
* @param settingsConsumer a {@link Consumer} of the configuration settings {@code Map}
|
||||||
|
* @param <T> the type of the {@link Settings}
|
||||||
|
* @return the {@link Settings}
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends Settings> T settings(Consumer<Map<String, Object>> settingsConsumer) {
|
||||||
|
settingsConsumer.accept(this.settings);
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.config;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A facility for token configuration settings.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.2
|
||||||
|
* @see Settings
|
||||||
|
*/
|
||||||
|
public class TokenSettings extends Settings {
|
||||||
|
private static final String TOKEN_SETTING_BASE = "setting.token.";
|
||||||
|
public static final String ACCESS_TOKEN_TIME_TO_LIVE = TOKEN_SETTING_BASE.concat("access-token-time-to-live");
|
||||||
|
public static final String ENABLE_REFRESH_TOKENS = TOKEN_SETTING_BASE.concat("enable-refresh-tokens");
|
||||||
|
public static final String REUSE_REFRESH_TOKENS = TOKEN_SETTING_BASE.concat("reuse-refresh-tokens");
|
||||||
|
public static final String REFRESH_TOKEN_TIME_TO_LIVE = TOKEN_SETTING_BASE.concat("refresh-token-time-to-live");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code TokenSettings}.
|
||||||
|
*/
|
||||||
|
public TokenSettings() {
|
||||||
|
this(defaultSettings());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code TokenSettings} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param settings the initial settings
|
||||||
|
*/
|
||||||
|
public TokenSettings(Map<String, Object> settings) {
|
||||||
|
super(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time-to-live for an access token. The default is 5 minutes.
|
||||||
|
*
|
||||||
|
* @return the time-to-live for an access token
|
||||||
|
*/
|
||||||
|
public Duration accessTokenTimeToLive() {
|
||||||
|
return setting(ACCESS_TOKEN_TIME_TO_LIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time-to-live for an access token. Must be greater than {@code Duration.ZERO}.
|
||||||
|
*
|
||||||
|
* @param accessTokenTimeToLive the time-to-live for an access token
|
||||||
|
* @return the {@link TokenSettings}
|
||||||
|
*/
|
||||||
|
public TokenSettings accessTokenTimeToLive(Duration accessTokenTimeToLive) {
|
||||||
|
Assert.notNull(accessTokenTimeToLive, "accessTokenTimeToLive cannot be null");
|
||||||
|
Assert.isTrue(accessTokenTimeToLive.getSeconds() > 0, "accessTokenTimeToLive must be greater than Duration.ZERO");
|
||||||
|
setting(ACCESS_TOKEN_TIME_TO_LIVE, accessTokenTimeToLive);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if refresh tokens are enabled. The default is {@code true}.
|
||||||
|
*
|
||||||
|
* @return {@code true} if refresh tokens are enabled, {@code false} otherwise
|
||||||
|
*/
|
||||||
|
public boolean enableRefreshTokens() {
|
||||||
|
return setting(ENABLE_REFRESH_TOKENS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to {@code true} to enable refresh tokens.
|
||||||
|
*
|
||||||
|
* @param enableRefreshTokens {@code true} to enable refresh tokens, {@code false} otherwise
|
||||||
|
* @return the {@link TokenSettings}
|
||||||
|
*/
|
||||||
|
public TokenSettings enableRefreshTokens(boolean enableRefreshTokens) {
|
||||||
|
setting(ENABLE_REFRESH_TOKENS, enableRefreshTokens);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if refresh tokens are reused when returning the access token response,
|
||||||
|
* or {@code false} if a new refresh token is issued. The default is {@code true}.
|
||||||
|
*/
|
||||||
|
public boolean reuseRefreshTokens() {
|
||||||
|
return setting(REUSE_REFRESH_TOKENS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to {@code true} if refresh tokens are reused when returning the access token response,
|
||||||
|
* or {@code false} if a new refresh token is issued.
|
||||||
|
*
|
||||||
|
* @param reuseRefreshTokens {@code true} to reuse refresh tokens, {@code false} to issue new refresh tokens
|
||||||
|
* @return the {@link TokenSettings}
|
||||||
|
*/
|
||||||
|
public TokenSettings reuseRefreshTokens(boolean reuseRefreshTokens) {
|
||||||
|
setting(REUSE_REFRESH_TOKENS, reuseRefreshTokens);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time-to-live for a refresh token. The default is 60 minutes.
|
||||||
|
*
|
||||||
|
* @return the time-to-live for a refresh token
|
||||||
|
*/
|
||||||
|
public Duration refreshTokenTimeToLive() {
|
||||||
|
return setting(REFRESH_TOKEN_TIME_TO_LIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time-to-live for a refresh token. Must be greater than {@code Duration.ZERO}.
|
||||||
|
*
|
||||||
|
* @param refreshTokenTimeToLive the time-to-live for a refresh token
|
||||||
|
* @return the {@link TokenSettings}
|
||||||
|
*/
|
||||||
|
public TokenSettings refreshTokenTimeToLive(Duration refreshTokenTimeToLive) {
|
||||||
|
Assert.notNull(refreshTokenTimeToLive, "refreshTokenTimeToLive cannot be null");
|
||||||
|
Assert.isTrue(refreshTokenTimeToLive.getSeconds() > 0, "refreshTokenTimeToLive must be greater than Duration.ZERO");
|
||||||
|
setting(REFRESH_TOKEN_TIME_TO_LIVE, refreshTokenTimeToLive);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Map<String, Object> defaultSettings() {
|
||||||
|
Map<String, Object> settings = new HashMap<>();
|
||||||
|
settings.put(ACCESS_TOKEN_TIME_TO_LIVE, Duration.ofMinutes(5));
|
||||||
|
settings.put(ENABLE_REFRESH_TOKENS, true);
|
||||||
|
settings.put(REUSE_REFRESH_TOKENS, true);
|
||||||
|
settings.put(REFRESH_TOKEN_TIME_TO_LIVE, Duration.ofMinutes(60));
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.token;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of an {@link AbstractOAuth2Token}
|
||||||
|
* representing an OAuth 2.0 Authorization Code Grant.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.3
|
||||||
|
* @see AbstractOAuth2Token
|
||||||
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant</a>
|
||||||
|
*/
|
||||||
|
public class OAuth2AuthorizationCode extends AbstractOAuth2Token {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2AuthorizationCode} using the provided parameters.
|
||||||
|
* @param tokenValue the token value
|
||||||
|
* @param issuedAt the time at which the token was issued
|
||||||
|
* @param expiresAt the time at which the token expires
|
||||||
|
*/
|
||||||
|
public OAuth2AuthorizationCode(String tokenValue, Instant issuedAt, Instant expiresAt) {
|
||||||
|
super(tokenValue, issuedAt, expiresAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.token;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.server.authorization.Version;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds metadata associated to an OAuth 2.0 Token.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.3
|
||||||
|
* @see OAuth2Tokens
|
||||||
|
*/
|
||||||
|
public class OAuth2TokenMetadata implements Serializable {
|
||||||
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
|
protected static final String TOKEN_METADATA_BASE = "metadata.token.";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the metadata that indicates if the token has been invalidated.
|
||||||
|
*/
|
||||||
|
public static final String INVALIDATED = TOKEN_METADATA_BASE.concat("invalidated");
|
||||||
|
|
||||||
|
private final Map<String, Object> metadata;
|
||||||
|
|
||||||
|
protected OAuth2TokenMetadata(Map<String, Object> metadata) {
|
||||||
|
this.metadata = Collections.unmodifiableMap(new HashMap<>(metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the token has been invalidated (e.g. revoked).
|
||||||
|
* The default is {@code false}.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the token has been invalidated, {@code false} otherwise
|
||||||
|
*/
|
||||||
|
public boolean isInvalidated() {
|
||||||
|
return getMetadata(INVALIDATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of the metadata associated to the token.
|
||||||
|
*
|
||||||
|
* @param name the name of the metadata
|
||||||
|
* @param <T> the type of the metadata
|
||||||
|
* @return the value of the metadata, or {@code null} if not available
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T getMetadata(String name) {
|
||||||
|
Assert.hasText(name, "name cannot be empty");
|
||||||
|
return (T) this.metadata.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the metadata associated to the token.
|
||||||
|
*
|
||||||
|
* @return a {@code Map} of the metadata
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getMetadata() {
|
||||||
|
return this.metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
OAuth2TokenMetadata that = (OAuth2TokenMetadata) obj;
|
||||||
|
return Objects.equals(this.metadata, that.metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(this.metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new {@link Builder}.
|
||||||
|
*
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for {@link OAuth2TokenMetadata}.
|
||||||
|
*/
|
||||||
|
public static class Builder implements Serializable {
|
||||||
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
|
private final Map<String, Object> metadata = defaultMetadata();
|
||||||
|
|
||||||
|
protected Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the token as invalidated (e.g. revoked).
|
||||||
|
*
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder invalidated() {
|
||||||
|
metadata(INVALIDATED, true);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a metadata associated to the token.
|
||||||
|
*
|
||||||
|
* @param name the name of the metadata
|
||||||
|
* @param value the value of the metadata
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder metadata(String name, Object value) {
|
||||||
|
Assert.hasText(name, "name cannot be empty");
|
||||||
|
Assert.notNull(value, "value cannot be null");
|
||||||
|
this.metadata.put(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code Consumer} of the metadata {@code Map}
|
||||||
|
* allowing the ability to add, replace, or remove.
|
||||||
|
*
|
||||||
|
* @param metadataConsumer a {@link Consumer} of the metadata {@code Map}
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder metadata(Consumer<Map<String, Object>> metadataConsumer) {
|
||||||
|
metadataConsumer.accept(this.metadata);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a new {@link OAuth2TokenMetadata}.
|
||||||
|
*
|
||||||
|
* @return the {@link OAuth2TokenMetadata}
|
||||||
|
*/
|
||||||
|
public OAuth2TokenMetadata build() {
|
||||||
|
return new OAuth2TokenMetadata(this.metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Map<String, Object> defaultMetadata() {
|
||||||
|
Map<String, Object> metadata = new HashMap<>();
|
||||||
|
metadata.put(INVALIDATED, false);
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.token;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.Version;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A container for OAuth 2.0 Tokens.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.3
|
||||||
|
* @see OAuth2Authorization
|
||||||
|
* @see OAuth2TokenMetadata
|
||||||
|
* @see AbstractOAuth2Token
|
||||||
|
* @see OAuth2AccessToken
|
||||||
|
* @see OAuth2RefreshToken
|
||||||
|
*/
|
||||||
|
public class OAuth2Tokens implements Serializable {
|
||||||
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
|
private final Map<Class<? extends AbstractOAuth2Token>, OAuth2TokenHolder> tokens;
|
||||||
|
|
||||||
|
protected OAuth2Tokens(Map<Class<? extends AbstractOAuth2Token>, OAuth2TokenHolder> tokens) {
|
||||||
|
this.tokens = new HashMap<>(tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link OAuth2AccessToken access token}.
|
||||||
|
*
|
||||||
|
* @return the {@link OAuth2AccessToken}, or {@code null} if not available
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public OAuth2AccessToken getAccessToken() {
|
||||||
|
return getToken(OAuth2AccessToken.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link OAuth2RefreshToken refresh token}.
|
||||||
|
*
|
||||||
|
* @return the {@link OAuth2RefreshToken}, or {@code null} if not available
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public OAuth2RefreshToken getRefreshToken() {
|
||||||
|
OAuth2RefreshToken refreshToken = getToken(OAuth2RefreshToken.class);
|
||||||
|
return refreshToken != null ? refreshToken : getToken(OAuth2RefreshToken2.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the token specified by {@code tokenType}.
|
||||||
|
*
|
||||||
|
* @param tokenType the token type
|
||||||
|
* @param <T> the type of the token
|
||||||
|
* @return the token, or {@code null} if not available
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends AbstractOAuth2Token> T getToken(Class<T> tokenType) {
|
||||||
|
Assert.notNull(tokenType, "tokenType cannot be null");
|
||||||
|
OAuth2TokenHolder tokenHolder = this.tokens.get(tokenType);
|
||||||
|
return tokenHolder != null ? (T) tokenHolder.getToken() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the token specified by {@code token}.
|
||||||
|
*
|
||||||
|
* @param token the token
|
||||||
|
* @param <T> the type of the token
|
||||||
|
* @return the token, or {@code null} if not available
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends AbstractOAuth2Token> T getToken(String token) {
|
||||||
|
Assert.hasText(token, "token cannot be empty");
|
||||||
|
OAuth2TokenHolder tokenHolder = this.tokens.values().stream()
|
||||||
|
.filter(holder -> holder.getToken().getTokenValue().equals(token))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
return tokenHolder != null ? (T) tokenHolder.getToken() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the token metadata associated to the provided {@code token}.
|
||||||
|
*
|
||||||
|
* @param token the token
|
||||||
|
* @param <T> the type of the token
|
||||||
|
* @return the token metadata, or {@code null} if not available
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public <T extends AbstractOAuth2Token> OAuth2TokenMetadata getTokenMetadata(T token) {
|
||||||
|
Assert.notNull(token, "token cannot be null");
|
||||||
|
OAuth2TokenHolder tokenHolder = this.tokens.get(token.getClass());
|
||||||
|
return (tokenHolder != null && tokenHolder.getToken().equals(token)) ?
|
||||||
|
tokenHolder.getTokenMetadata() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
OAuth2Tokens that = (OAuth2Tokens) obj;
|
||||||
|
return Objects.equals(this.tokens, that.tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(this.tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new {@link Builder}.
|
||||||
|
*
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new {@link Builder}, initialized with the values from the provided {@code tokens}.
|
||||||
|
*
|
||||||
|
* @param tokens the tokens used for initializing the {@link Builder}
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public static Builder from(OAuth2Tokens tokens) {
|
||||||
|
Assert.notNull(tokens, "tokens cannot be null");
|
||||||
|
return new Builder(tokens.tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for {@link OAuth2Tokens}.
|
||||||
|
*/
|
||||||
|
public static class Builder implements Serializable {
|
||||||
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
|
private Map<Class<? extends AbstractOAuth2Token>, OAuth2TokenHolder> tokens;
|
||||||
|
|
||||||
|
protected Builder() {
|
||||||
|
this.tokens = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Builder(Map<Class<? extends AbstractOAuth2Token>, OAuth2TokenHolder> tokens) {
|
||||||
|
this.tokens = new HashMap<>(tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link OAuth2AccessToken access token}.
|
||||||
|
*
|
||||||
|
* @param accessToken the {@link OAuth2AccessToken}
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder accessToken(OAuth2AccessToken accessToken) {
|
||||||
|
return addToken(accessToken, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link OAuth2AccessToken access token} and associated {@link OAuth2TokenMetadata token metadata}.
|
||||||
|
*
|
||||||
|
* @param accessToken the {@link OAuth2AccessToken}
|
||||||
|
* @param tokenMetadata the {@link OAuth2TokenMetadata}
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder accessToken(OAuth2AccessToken accessToken, OAuth2TokenMetadata tokenMetadata) {
|
||||||
|
return addToken(accessToken, tokenMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link OAuth2RefreshToken refresh token}.
|
||||||
|
*
|
||||||
|
* @param refreshToken the {@link OAuth2RefreshToken}
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder refreshToken(OAuth2RefreshToken refreshToken) {
|
||||||
|
return addToken(refreshToken, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link OAuth2RefreshToken refresh token} and associated {@link OAuth2TokenMetadata token metadata}.
|
||||||
|
*
|
||||||
|
* @param refreshToken the {@link OAuth2RefreshToken}
|
||||||
|
* @param tokenMetadata the {@link OAuth2TokenMetadata}
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public Builder refreshToken(OAuth2RefreshToken refreshToken, OAuth2TokenMetadata tokenMetadata) {
|
||||||
|
return addToken(refreshToken, tokenMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the token.
|
||||||
|
*
|
||||||
|
* @param token the token
|
||||||
|
* @param <T> the type of the token
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public <T extends AbstractOAuth2Token> Builder token(T token) {
|
||||||
|
return addToken(token, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the token and associated {@link OAuth2TokenMetadata token metadata}.
|
||||||
|
*
|
||||||
|
* @param token the token
|
||||||
|
* @param tokenMetadata the {@link OAuth2TokenMetadata}
|
||||||
|
* @param <T> the type of the token
|
||||||
|
* @return the {@link Builder}
|
||||||
|
*/
|
||||||
|
public <T extends AbstractOAuth2Token> Builder token(T token, OAuth2TokenMetadata tokenMetadata) {
|
||||||
|
return addToken(token, tokenMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Builder addToken(AbstractOAuth2Token token, OAuth2TokenMetadata tokenMetadata) {
|
||||||
|
Assert.notNull(token, "token cannot be null");
|
||||||
|
if (tokenMetadata == null) {
|
||||||
|
tokenMetadata = OAuth2TokenMetadata.builder().build();
|
||||||
|
}
|
||||||
|
this.tokens.put(token.getClass(), new OAuth2TokenHolder(token, tokenMetadata));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a new {@link OAuth2Tokens}.
|
||||||
|
*
|
||||||
|
* @return the {@link OAuth2Tokens}
|
||||||
|
*/
|
||||||
|
public OAuth2Tokens build() {
|
||||||
|
return new OAuth2Tokens(this.tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class OAuth2TokenHolder implements Serializable {
|
||||||
|
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||||
|
private final AbstractOAuth2Token token;
|
||||||
|
private final OAuth2TokenMetadata tokenMetadata;
|
||||||
|
|
||||||
|
protected OAuth2TokenHolder(AbstractOAuth2Token token, OAuth2TokenMetadata tokenMetadata) {
|
||||||
|
this.token = token;
|
||||||
|
this.tokenMetadata = tokenMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AbstractOAuth2Token getToken() {
|
||||||
|
return this.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected OAuth2TokenMetadata getTokenMetadata() {
|
||||||
|
return this.tokenMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
OAuth2TokenHolder that = (OAuth2TokenHolder) obj;
|
||||||
|
return Objects.equals(this.token, that.token) &&
|
||||||
|
Objects.equals(this.tokenMetadata, that.tokenMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(this.token, this.tokenMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,9 @@ import javax.servlet.http.HttpServletRequest;
|
|||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to extract HTTP Basic credentials from {@link HttpServletRequest}
|
* Attempts to extract HTTP Basic credentials from {@link HttpServletRequest}
|
||||||
@@ -82,6 +85,15 @@ public class ClientSecretBasicAuthenticationConverter implements AuthenticationC
|
|||||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST), ex);
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST), ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OAuth2ClientAuthenticationToken(clientID, clientSecret);
|
return new OAuth2ClientAuthenticationToken(clientID, clientSecret, extractAdditionalParameters(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> extractAdditionalParameters(HttpServletRequest request) {
|
||||||
|
Map<String, Object> additionalParameters = Collections.emptyMap();
|
||||||
|
if (OAuth2EndpointUtils.matchesPkceTokenRequest(request)) {
|
||||||
|
// Confidential clients can also leverage PKCE
|
||||||
|
additionalParameters = new HashMap<>(OAuth2EndpointUtils.getParameters(request).toSingleValueMap());
|
||||||
|
}
|
||||||
|
return additionalParameters;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.web;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AuthenticationConverter} that simply delegates to it's
|
||||||
|
* internal {@code List} of {@link AuthenticationConverter}(s).
|
||||||
|
* <p>
|
||||||
|
* Each {@link AuthenticationConverter} is given a chance to
|
||||||
|
* {@link AuthenticationConverter#convert(HttpServletRequest)}
|
||||||
|
* with the first {@code non-null} {@link Authentication} being returned.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.2
|
||||||
|
* @see AuthenticationConverter
|
||||||
|
*/
|
||||||
|
public final class DelegatingAuthenticationConverter implements AuthenticationConverter {
|
||||||
|
private final List<AuthenticationConverter> converters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code DelegatingAuthenticationConverter} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param converters a {@code List} of {@link AuthenticationConverter}(s)
|
||||||
|
*/
|
||||||
|
public DelegatingAuthenticationConverter(List<AuthenticationConverter> converters) {
|
||||||
|
Assert.notEmpty(converters, "converters cannot be empty");
|
||||||
|
this.converters = Collections.unmodifiableList(new LinkedList<>(converters));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Authentication convert(HttpServletRequest request) {
|
||||||
|
Assert.notNull(request, "request cannot be null");
|
||||||
|
// @formatter:off
|
||||||
|
return this.converters.stream()
|
||||||
|
.map(converter -> converter.convert(request))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
package org.springframework.security.oauth2.server.authorization.web;
|
package org.springframework.security.oauth2.server.authorization.web;
|
||||||
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
@@ -48,6 +49,7 @@ public final class DelegatingAuthorizationGrantAuthenticationConverter implement
|
|||||||
this.converters = Collections.unmodifiableMap(new HashMap<>(converters));
|
this.converters = Collections.unmodifiableMap(new HashMap<>(converters));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Authentication convert(HttpServletRequest request) {
|
public Authentication convert(HttpServletRequest request) {
|
||||||
Assert.notNull(request, "request cannot be null");
|
Assert.notNull(request, "request cannot be null");
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public class JwkSetEndpointFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
try (Writer writer = response.getWriter()) {
|
try (Writer writer = response.getWriter()) {
|
||||||
writer.write(jwkSet.toJSONObject().toString());
|
writer.write(jwkSet.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package org.springframework.security.oauth2.server.authorization.web;
|
|||||||
|
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
@@ -28,16 +29,21 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
|||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
||||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||||
import org.springframework.security.web.RedirectStrategy;
|
import org.springframework.security.web.RedirectStrategy;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
@@ -48,10 +54,14 @@ import javax.servlet.ServletException;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,12 +70,14 @@ import java.util.Set;
|
|||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @author Paurav Munshi
|
* @author Paurav Munshi
|
||||||
|
* @author Daniel Garnier-Moiroux
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
* @see RegisteredClientRepository
|
* @see RegisteredClientRepository
|
||||||
* @see OAuth2AuthorizationService
|
* @see OAuth2AuthorizationService
|
||||||
* @see OAuth2Authorization
|
* @see OAuth2Authorization
|
||||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant</a>
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant</a>
|
||||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.1">Section 4.1.1 Authorization Request</a>
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.1">Section 4.1.1 Authorization Request</a>
|
||||||
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.2">Section 4.1.2 Authorization Response</a>
|
||||||
*/
|
*/
|
||||||
public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||||
/**
|
/**
|
||||||
@@ -73,10 +85,14 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
|||||||
*/
|
*/
|
||||||
public static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize";
|
public static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize";
|
||||||
|
|
||||||
|
private static final String PKCE_ERROR_URI = "https://tools.ietf.org/html/rfc7636#section-4.4.1";
|
||||||
|
|
||||||
private final RegisteredClientRepository registeredClientRepository;
|
private final RegisteredClientRepository registeredClientRepository;
|
||||||
private final OAuth2AuthorizationService authorizationService;
|
private final OAuth2AuthorizationService authorizationService;
|
||||||
private final RequestMatcher authorizationEndpointMatcher;
|
private final RequestMatcher authorizationRequestMatcher;
|
||||||
private final StringKeyGenerator codeGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
|
private final RequestMatcher userConsentMatcher;
|
||||||
|
private final StringKeyGenerator codeGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
|
||||||
|
private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
|
||||||
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
|
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,73 +120,42 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
|||||||
Assert.hasText(authorizationEndpointUri, "authorizationEndpointUri cannot be empty");
|
Assert.hasText(authorizationEndpointUri, "authorizationEndpointUri cannot be empty");
|
||||||
this.registeredClientRepository = registeredClientRepository;
|
this.registeredClientRepository = registeredClientRepository;
|
||||||
this.authorizationService = authorizationService;
|
this.authorizationService = authorizationService;
|
||||||
this.authorizationEndpointMatcher = new AntPathRequestMatcher(
|
this.authorizationRequestMatcher = new AntPathRequestMatcher(
|
||||||
authorizationEndpointUri, HttpMethod.GET.name());
|
authorizationEndpointUri, HttpMethod.GET.name());
|
||||||
|
this.userConsentMatcher = new AntPathRequestMatcher(
|
||||||
|
authorizationEndpointUri, HttpMethod.POST.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
|
|
||||||
if (!this.authorizationEndpointMatcher.matches(request)) {
|
if (this.authorizationRequestMatcher.matches(request)) {
|
||||||
|
processAuthorizationRequest(request, response, filterChain);
|
||||||
|
} else if (this.userConsentMatcher.matches(request)) {
|
||||||
|
processUserConsent(request, response);
|
||||||
|
} else {
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------
|
private void processAuthorizationRequest(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
// Validate the request to ensure that all required parameters are present and valid
|
throws ServletException, IOException {
|
||||||
// ---------------
|
|
||||||
|
|
||||||
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
|
OAuth2AuthorizationRequestContext authorizationRequestContext =
|
||||||
String stateParameter = parameters.getFirst(OAuth2ParameterNames.STATE);
|
new OAuth2AuthorizationRequestContext(
|
||||||
|
request.getRequestURL().toString(),
|
||||||
|
OAuth2EndpointUtils.getParameters(request));
|
||||||
|
|
||||||
// client_id (REQUIRED)
|
validateAuthorizationRequest(authorizationRequestContext);
|
||||||
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
|
|
||||||
if (!StringUtils.hasText(clientId) ||
|
if (authorizationRequestContext.hasError()) {
|
||||||
parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
|
if (authorizationRequestContext.isRedirectOnError()) {
|
||||||
OAuth2Error error = createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);
|
sendErrorResponse(request, response, authorizationRequestContext.resolveRedirectUri(),
|
||||||
sendErrorResponse(request, response, error, stateParameter, null); // when redirectUri is null then don't redirect
|
authorizationRequestContext.getError(), authorizationRequestContext.getState());
|
||||||
return;
|
} else {
|
||||||
|
sendErrorResponse(response, authorizationRequestContext.getError());
|
||||||
}
|
}
|
||||||
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
|
|
||||||
if (registeredClient == null) {
|
|
||||||
OAuth2Error error = createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);
|
|
||||||
sendErrorResponse(request, response, error, stateParameter, null); // when redirectUri is null then don't redirect
|
|
||||||
return;
|
|
||||||
} else if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
|
|
||||||
OAuth2Error error = createError(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID);
|
|
||||||
sendErrorResponse(request, response, error, stateParameter, null); // when redirectUri is null then don't redirect
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// redirect_uri (OPTIONAL)
|
|
||||||
String redirectUriParameter = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
|
|
||||||
if (StringUtils.hasText(redirectUriParameter)) {
|
|
||||||
if (!registeredClient.getRedirectUris().contains(redirectUriParameter) ||
|
|
||||||
parameters.get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) {
|
|
||||||
OAuth2Error error = createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI);
|
|
||||||
sendErrorResponse(request, response, error, stateParameter, null); // when redirectUri is null then don't redirect
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (registeredClient.getRedirectUris().size() != 1) {
|
|
||||||
OAuth2Error error = createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI);
|
|
||||||
sendErrorResponse(request, response, error, stateParameter, null); // when redirectUri is null then don't redirect
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String redirectUri = StringUtils.hasText(redirectUriParameter) ?
|
|
||||||
redirectUriParameter : registeredClient.getRedirectUris().iterator().next();
|
|
||||||
|
|
||||||
// response_type (REQUIRED)
|
|
||||||
String responseType = parameters.getFirst(OAuth2ParameterNames.RESPONSE_TYPE);
|
|
||||||
if (!StringUtils.hasText(responseType) ||
|
|
||||||
parameters.get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) {
|
|
||||||
OAuth2Error error = createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE);
|
|
||||||
sendErrorResponse(request, response, error, stateParameter, redirectUri);
|
|
||||||
return;
|
|
||||||
} else if (!responseType.equals(OAuth2AuthorizationResponseType.CODE.getValue())) {
|
|
||||||
OAuth2Error error = createError(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, OAuth2ParameterNames.RESPONSE_TYPE);
|
|
||||||
sendErrorResponse(request, response, error, stateParameter, redirectUri);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,15 +171,31 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String code = this.codeGenerator.generateKey();
|
RegisteredClient registeredClient = authorizationRequestContext.getRegisteredClient();
|
||||||
OAuth2AuthorizationRequest authorizationRequest = convertAuthorizationRequest(request);
|
OAuth2AuthorizationRequest authorizationRequest = authorizationRequestContext.buildAuthorizationRequest();
|
||||||
|
OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||||
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
|
|
||||||
.principalName(principal.getName())
|
.principalName(principal.getName())
|
||||||
.attribute(OAuth2AuthorizationAttributeNames.CODE, code)
|
.attribute(OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST, authorizationRequest);
|
||||||
.attribute(OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST, authorizationRequest)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
|
if (registeredClient.getClientSettings().requireUserConsent()) {
|
||||||
|
String state = this.stateGenerator.generateKey();
|
||||||
|
OAuth2Authorization authorization = builder
|
||||||
|
.attribute(OAuth2AuthorizationAttributeNames.STATE, state)
|
||||||
|
.build();
|
||||||
|
this.authorizationService.save(authorization);
|
||||||
|
|
||||||
|
// TODO Need to remove 'in-flight' authorization if consent step is not completed (e.g. approved or cancelled)
|
||||||
|
|
||||||
|
UserConsentPage.displayConsent(request, response, registeredClient, authorization);
|
||||||
|
} else {
|
||||||
|
Instant issuedAt = Instant.now();
|
||||||
|
Instant expiresAt = issuedAt.plus(5, ChronoUnit.MINUTES); // TODO Allow configuration for authorization code time-to-live
|
||||||
|
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
|
||||||
|
this.codeGenerator.generateKey(), issuedAt, expiresAt);
|
||||||
|
OAuth2Authorization authorization = builder
|
||||||
|
.tokens(OAuth2Tokens.builder().token(authorizationCode).build())
|
||||||
|
.attribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES, authorizationRequest.getScopes())
|
||||||
|
.build();
|
||||||
this.authorizationService.save(authorization);
|
this.authorizationService.save(authorization);
|
||||||
|
|
||||||
// TODO security checks for code parameter
|
// TODO security checks for code parameter
|
||||||
@@ -205,29 +206,212 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
|||||||
// and SHOULD revoke (when possible) all tokens previously issued based on that authorization code.
|
// and SHOULD revoke (when possible) all tokens previously issued based on that authorization code.
|
||||||
// The authorization code is bound to the client identifier and redirection URI.
|
// The authorization code is bound to the client identifier and redirection URI.
|
||||||
|
|
||||||
sendAuthorizationResponse(request, response, authorizationRequest, code, redirectUri);
|
sendAuthorizationResponse(request, response,
|
||||||
|
authorizationRequestContext.resolveRedirectUri(), authorizationCode, authorizationRequest.getState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processUserConsent(HttpServletRequest request, HttpServletResponse response)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
UserConsentRequestContext userConsentRequestContext =
|
||||||
|
new UserConsentRequestContext(
|
||||||
|
request.getRequestURL().toString(),
|
||||||
|
OAuth2EndpointUtils.getParameters(request));
|
||||||
|
|
||||||
|
validateUserConsentRequest(userConsentRequestContext);
|
||||||
|
|
||||||
|
if (userConsentRequestContext.hasError()) {
|
||||||
|
if (userConsentRequestContext.isRedirectOnError()) {
|
||||||
|
sendErrorResponse(request, response, userConsentRequestContext.resolveRedirectUri(),
|
||||||
|
userConsentRequestContext.getError(), userConsentRequestContext.getState());
|
||||||
|
} else {
|
||||||
|
sendErrorResponse(response, userConsentRequestContext.getError());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!UserConsentPage.isConsentApproved(request)) {
|
||||||
|
this.authorizationService.remove(userConsentRequestContext.getAuthorization());
|
||||||
|
OAuth2Error error = createError(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID);
|
||||||
|
sendErrorResponse(request, response, userConsentRequestContext.resolveRedirectUri(),
|
||||||
|
error, userConsentRequestContext.getAuthorizationRequest().getState());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instant issuedAt = Instant.now();
|
||||||
|
Instant expiresAt = issuedAt.plus(5, ChronoUnit.MINUTES); // TODO Allow configuration for authorization code time-to-live
|
||||||
|
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
|
||||||
|
this.codeGenerator.generateKey(), issuedAt, expiresAt);
|
||||||
|
OAuth2Authorization authorization = OAuth2Authorization.from(userConsentRequestContext.getAuthorization())
|
||||||
|
.tokens(OAuth2Tokens.builder().token(authorizationCode).build())
|
||||||
|
.attributes(attrs -> {
|
||||||
|
attrs.remove(OAuth2AuthorizationAttributeNames.STATE);
|
||||||
|
attrs.put(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES, userConsentRequestContext.getScopes());
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
this.authorizationService.save(authorization);
|
||||||
|
|
||||||
|
sendAuthorizationResponse(request, response, userConsentRequestContext.resolveRedirectUri(),
|
||||||
|
authorizationCode, userConsentRequestContext.getAuthorizationRequest().getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateAuthorizationRequest(OAuth2AuthorizationRequestContext authorizationRequestContext) {
|
||||||
|
// ---------------
|
||||||
|
// Validate the request to ensure all required parameters are present and valid
|
||||||
|
// ---------------
|
||||||
|
|
||||||
|
// client_id (REQUIRED)
|
||||||
|
if (!StringUtils.hasText(authorizationRequestContext.getClientId()) ||
|
||||||
|
authorizationRequestContext.getParameters().get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
|
||||||
|
authorizationRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
|
||||||
|
authorizationRequestContext.getClientId());
|
||||||
|
if (registeredClient == null) {
|
||||||
|
authorizationRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID));
|
||||||
|
return;
|
||||||
|
} else if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
|
||||||
|
authorizationRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
authorizationRequestContext.setRegisteredClient(registeredClient);
|
||||||
|
|
||||||
|
// redirect_uri (OPTIONAL)
|
||||||
|
if (StringUtils.hasText(authorizationRequestContext.getRedirectUri())) {
|
||||||
|
if (!registeredClient.getRedirectUris().contains(authorizationRequestContext.getRedirectUri()) ||
|
||||||
|
authorizationRequestContext.getParameters().get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) {
|
||||||
|
authorizationRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (registeredClient.getRedirectUris().size() != 1) {
|
||||||
|
authorizationRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
authorizationRequestContext.setRedirectOnError(true);
|
||||||
|
|
||||||
|
// response_type (REQUIRED)
|
||||||
|
if (!StringUtils.hasText(authorizationRequestContext.getResponseType()) ||
|
||||||
|
authorizationRequestContext.getParameters().get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) {
|
||||||
|
authorizationRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE));
|
||||||
|
return;
|
||||||
|
} else if (!authorizationRequestContext.getResponseType().equals(OAuth2AuthorizationResponseType.CODE.getValue())) {
|
||||||
|
authorizationRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, OAuth2ParameterNames.RESPONSE_TYPE));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// scope (OPTIONAL)
|
||||||
|
Set<String> requestedScopes = authorizationRequestContext.getScopes();
|
||||||
|
Set<String> allowedScopes = registeredClient.getScopes();
|
||||||
|
if (!requestedScopes.isEmpty() && !allowedScopes.containsAll(requestedScopes)) {
|
||||||
|
authorizationRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
|
||||||
|
String codeChallenge = authorizationRequestContext.getParameters().getFirst(PkceParameterNames.CODE_CHALLENGE);
|
||||||
|
if (StringUtils.hasText(codeChallenge)) {
|
||||||
|
if (authorizationRequestContext.getParameters().get(PkceParameterNames.CODE_CHALLENGE).size() != 1) {
|
||||||
|
authorizationRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String codeChallengeMethod = authorizationRequestContext.getParameters().getFirst(PkceParameterNames.CODE_CHALLENGE_METHOD);
|
||||||
|
if (StringUtils.hasText(codeChallengeMethod)) {
|
||||||
|
if (authorizationRequestContext.getParameters().get(PkceParameterNames.CODE_CHALLENGE_METHOD).size() != 1 ||
|
||||||
|
(!"S256".equals(codeChallengeMethod) && !"plain".equals(codeChallengeMethod))) {
|
||||||
|
authorizationRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (registeredClient.getClientSettings().requireProofKey()) {
|
||||||
|
authorizationRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateUserConsentRequest(UserConsentRequestContext userConsentRequestContext) {
|
||||||
|
// ---------------
|
||||||
|
// Validate the request to ensure all required parameters are present and valid
|
||||||
|
// ---------------
|
||||||
|
|
||||||
|
// state (REQUIRED)
|
||||||
|
if (!StringUtils.hasText(userConsentRequestContext.getState()) ||
|
||||||
|
userConsentRequestContext.getParameters().get(OAuth2ParameterNames.STATE).size() != 1) {
|
||||||
|
userConsentRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OAuth2Authorization authorization = this.authorizationService.findByToken(
|
||||||
|
userConsentRequestContext.getState(), new TokenType(OAuth2AuthorizationAttributeNames.STATE));
|
||||||
|
if (authorization == null) {
|
||||||
|
userConsentRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userConsentRequestContext.setAuthorization(authorization);
|
||||||
|
|
||||||
|
// The 'in-flight' authorization must be associated to the current principal
|
||||||
|
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
if (!isPrincipalAuthenticated(principal) || !principal.getName().equals(authorization.getPrincipalName())) {
|
||||||
|
userConsentRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// client_id (REQUIRED)
|
||||||
|
if (!StringUtils.hasText(userConsentRequestContext.getClientId()) ||
|
||||||
|
userConsentRequestContext.getParameters().get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
|
||||||
|
userConsentRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
|
||||||
|
userConsentRequestContext.getClientId());
|
||||||
|
if (registeredClient == null || !registeredClient.getId().equals(authorization.getRegisteredClientId())) {
|
||||||
|
userConsentRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userConsentRequestContext.setRegisteredClient(registeredClient);
|
||||||
|
userConsentRequestContext.setRedirectOnError(true);
|
||||||
|
|
||||||
|
// scope (OPTIONAL)
|
||||||
|
Set<String> requestedScopes = userConsentRequestContext.getAuthorizationRequest().getScopes();
|
||||||
|
Set<String> authorizedScopes = userConsentRequestContext.getScopes();
|
||||||
|
if (!authorizedScopes.isEmpty() && !requestedScopes.containsAll(authorizedScopes)) {
|
||||||
|
userConsentRequestContext.setError(
|
||||||
|
createError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendAuthorizationResponse(HttpServletRequest request, HttpServletResponse response,
|
private void sendAuthorizationResponse(HttpServletRequest request, HttpServletResponse response,
|
||||||
OAuth2AuthorizationRequest authorizationRequest, String code, String redirectUri) throws IOException {
|
String redirectUri, OAuth2AuthorizationCode authorizationCode, String state) throws IOException {
|
||||||
|
|
||||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder
|
UriComponentsBuilder uriBuilder = UriComponentsBuilder
|
||||||
.fromUriString(redirectUri)
|
.fromUriString(redirectUri)
|
||||||
.queryParam(OAuth2ParameterNames.CODE, code);
|
.queryParam(OAuth2ParameterNames.CODE, authorizationCode.getTokenValue());
|
||||||
if (StringUtils.hasText(authorizationRequest.getState())) {
|
if (StringUtils.hasText(state)) {
|
||||||
uriBuilder.queryParam(OAuth2ParameterNames.STATE, authorizationRequest.getState());
|
uriBuilder.queryParam(OAuth2ParameterNames.STATE, state);
|
||||||
}
|
}
|
||||||
this.redirectStrategy.sendRedirect(request, response, uriBuilder.toUriString());
|
this.redirectStrategy.sendRedirect(request, response, uriBuilder.toUriString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response,
|
private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response,
|
||||||
OAuth2Error error, String state, String redirectUri) throws IOException {
|
String redirectUri, OAuth2Error error, String state) throws IOException {
|
||||||
|
|
||||||
if (redirectUri == null) {
|
|
||||||
// TODO Send default html error response
|
|
||||||
response.sendError(HttpStatus.BAD_REQUEST.value(), error.toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder
|
UriComponentsBuilder uriBuilder = UriComponentsBuilder
|
||||||
.fromUriString(redirectUri)
|
.fromUriString(redirectUri)
|
||||||
@@ -244,9 +428,17 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
|||||||
this.redirectStrategy.sendRedirect(request, response, uriBuilder.toUriString());
|
this.redirectStrategy.sendRedirect(request, response, uriBuilder.toUriString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendErrorResponse(HttpServletResponse response, OAuth2Error error) throws IOException {
|
||||||
|
// TODO Send default html error response
|
||||||
|
response.sendError(HttpStatus.BAD_REQUEST.value(), error.toString());
|
||||||
|
}
|
||||||
|
|
||||||
private static OAuth2Error createError(String errorCode, String parameterName) {
|
private static OAuth2Error createError(String errorCode, String parameterName) {
|
||||||
return new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName,
|
return createError(errorCode, parameterName, "https://tools.ietf.org/html/rfc6749#section-4.1.2.1");
|
||||||
"https://tools.ietf.org/html/rfc6749#section-4.1.2.1");
|
}
|
||||||
|
|
||||||
|
private static OAuth2Error createError(String errorCode, String parameterName, String errorUri) {
|
||||||
|
return new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isPrincipalAuthenticated(Authentication principal) {
|
private static boolean isPrincipalAuthenticated(Authentication principal) {
|
||||||
@@ -255,23 +447,50 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
|||||||
principal.isAuthenticated();
|
principal.isAuthenticated();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OAuth2AuthorizationRequest convertAuthorizationRequest(HttpServletRequest request) {
|
private static class OAuth2AuthorizationRequestContext extends AbstractRequestContext {
|
||||||
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
|
private final String responseType;
|
||||||
|
private final String redirectUri;
|
||||||
|
|
||||||
Set<String> scopes = Collections.emptySet();
|
private OAuth2AuthorizationRequestContext(
|
||||||
if (parameters.containsKey(OAuth2ParameterNames.SCOPE)) {
|
String authorizationUri, MultiValueMap<String, String> parameters) {
|
||||||
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
|
super(authorizationUri, parameters,
|
||||||
scopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
|
parameters.getFirst(OAuth2ParameterNames.CLIENT_ID),
|
||||||
|
parameters.getFirst(OAuth2ParameterNames.STATE),
|
||||||
|
extractScopes(parameters));
|
||||||
|
this.responseType = parameters.getFirst(OAuth2ParameterNames.RESPONSE_TYPE);
|
||||||
|
this.redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Set<String> extractScopes(MultiValueMap<String, String> parameters) {
|
||||||
|
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
|
||||||
|
return StringUtils.hasText(scope) ?
|
||||||
|
new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))) :
|
||||||
|
Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getResponseType() {
|
||||||
|
return this.responseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRedirectUri() {
|
||||||
|
return this.redirectUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String resolveRedirectUri() {
|
||||||
|
return StringUtils.hasText(getRedirectUri()) ?
|
||||||
|
getRedirectUri() :
|
||||||
|
getRegisteredClient().getRedirectUris().iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private OAuth2AuthorizationRequest buildAuthorizationRequest() {
|
||||||
return OAuth2AuthorizationRequest.authorizationCode()
|
return OAuth2AuthorizationRequest.authorizationCode()
|
||||||
.authorizationUri(request.getRequestURL().toString())
|
.authorizationUri(getAuthorizationUri())
|
||||||
.clientId(parameters.getFirst(OAuth2ParameterNames.CLIENT_ID))
|
.clientId(getClientId())
|
||||||
.redirectUri(parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI))
|
.redirectUri(getRedirectUri())
|
||||||
.scopes(scopes)
|
.scopes(getScopes())
|
||||||
.state(parameters.getFirst(OAuth2ParameterNames.STATE))
|
.state(getState())
|
||||||
.additionalParameters(additionalParameters ->
|
.additionalParameters(additionalParameters ->
|
||||||
parameters.entrySet().stream()
|
getParameters().entrySet().stream()
|
||||||
.filter(e -> !e.getKey().equals(OAuth2ParameterNames.RESPONSE_TYPE) &&
|
.filter(e -> !e.getKey().equals(OAuth2ParameterNames.RESPONSE_TYPE) &&
|
||||||
!e.getKey().equals(OAuth2ParameterNames.CLIENT_ID) &&
|
!e.getKey().equals(OAuth2ParameterNames.CLIENT_ID) &&
|
||||||
!e.getKey().equals(OAuth2ParameterNames.REDIRECT_URI) &&
|
!e.getKey().equals(OAuth2ParameterNames.REDIRECT_URI) &&
|
||||||
@@ -281,3 +500,201 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class UserConsentRequestContext extends AbstractRequestContext {
|
||||||
|
private OAuth2Authorization authorization;
|
||||||
|
|
||||||
|
private UserConsentRequestContext(
|
||||||
|
String authorizationUri, MultiValueMap<String, String> parameters) {
|
||||||
|
super(authorizationUri, parameters,
|
||||||
|
parameters.getFirst(OAuth2ParameterNames.CLIENT_ID),
|
||||||
|
parameters.getFirst(OAuth2ParameterNames.STATE),
|
||||||
|
extractScopes(parameters));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> extractScopes(MultiValueMap<String, String> parameters) {
|
||||||
|
List<String> scope = parameters.get(OAuth2ParameterNames.SCOPE);
|
||||||
|
return !CollectionUtils.isEmpty(scope) ? new HashSet<>(scope) : Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private OAuth2Authorization getAuthorization() {
|
||||||
|
return this.authorization;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAuthorization(OAuth2Authorization authorization) {
|
||||||
|
this.authorization = authorization;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String resolveRedirectUri() {
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = getAuthorizationRequest();
|
||||||
|
return StringUtils.hasText(authorizationRequest.getRedirectUri()) ?
|
||||||
|
authorizationRequest.getRedirectUri() :
|
||||||
|
getRegisteredClient().getRedirectUris().iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private OAuth2AuthorizationRequest getAuthorizationRequest() {
|
||||||
|
return getAuthorization().getAttribute(OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract static class AbstractRequestContext {
|
||||||
|
private final String authorizationUri;
|
||||||
|
private final MultiValueMap<String, String> parameters;
|
||||||
|
private final String clientId;
|
||||||
|
private final String state;
|
||||||
|
private final Set<String> scopes;
|
||||||
|
private RegisteredClient registeredClient;
|
||||||
|
private OAuth2Error error;
|
||||||
|
private boolean redirectOnError;
|
||||||
|
|
||||||
|
protected AbstractRequestContext(String authorizationUri, MultiValueMap<String, String> parameters,
|
||||||
|
String clientId, String state, Set<String> scopes) {
|
||||||
|
this.authorizationUri = authorizationUri;
|
||||||
|
this.parameters = parameters;
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.state = state;
|
||||||
|
this.scopes = scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getAuthorizationUri() {
|
||||||
|
return this.authorizationUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MultiValueMap<String, String> getParameters() {
|
||||||
|
return this.parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getClientId() {
|
||||||
|
return this.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getState() {
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set<String> getScopes() {
|
||||||
|
return this.scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RegisteredClient getRegisteredClient() {
|
||||||
|
return this.registeredClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setRegisteredClient(RegisteredClient registeredClient) {
|
||||||
|
this.registeredClient = registeredClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected OAuth2Error getError() {
|
||||||
|
return this.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setError(OAuth2Error error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean hasError() {
|
||||||
|
return getError() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isRedirectOnError() {
|
||||||
|
return this.redirectOnError;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setRedirectOnError(boolean redirectOnError) {
|
||||||
|
this.redirectOnError = redirectOnError;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract String resolveRedirectUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class UserConsentPage {
|
||||||
|
private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
|
||||||
|
private static final String CONSENT_ACTION_PARAMETER_NAME = "consent_action";
|
||||||
|
private static final String CONSENT_ACTION_APPROVE = "approve";
|
||||||
|
private static final String CONSENT_ACTION_CANCEL = "cancel";
|
||||||
|
|
||||||
|
private static void displayConsent(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
RegisteredClient registeredClient, OAuth2Authorization authorization) throws IOException {
|
||||||
|
|
||||||
|
String consentPage = generateConsentPage(request, registeredClient, authorization);
|
||||||
|
response.setContentType(TEXT_HTML_UTF8.toString());
|
||||||
|
response.setContentLength(consentPage.getBytes(StandardCharsets.UTF_8).length);
|
||||||
|
response.getWriter().write(consentPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isConsentApproved(HttpServletRequest request) {
|
||||||
|
return CONSENT_ACTION_APPROVE.equalsIgnoreCase(request.getParameter(CONSENT_ACTION_PARAMETER_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isConsentCancelled(HttpServletRequest request) {
|
||||||
|
return CONSENT_ACTION_CANCEL.equalsIgnoreCase(request.getParameter(CONSENT_ACTION_PARAMETER_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generateConsentPage(HttpServletRequest request,
|
||||||
|
RegisteredClient registeredClient, OAuth2Authorization authorization) {
|
||||||
|
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||||
|
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
|
||||||
|
String state = authorization.getAttribute(
|
||||||
|
OAuth2AuthorizationAttributeNames.STATE);
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.append("<!DOCTYPE html>");
|
||||||
|
builder.append("<html lang=\"en\">");
|
||||||
|
builder.append("<head>");
|
||||||
|
builder.append(" <meta charset=\"utf-8\">");
|
||||||
|
builder.append(" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">");
|
||||||
|
builder.append(" <link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css\" integrity=\"sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z\" crossorigin=\"anonymous\">");
|
||||||
|
builder.append(" <title>Consent required</title>");
|
||||||
|
builder.append("</head>");
|
||||||
|
builder.append("<body>");
|
||||||
|
builder.append("<div class=\"container\">");
|
||||||
|
builder.append(" <div class=\"py-5\">");
|
||||||
|
builder.append(" <h1 class=\"text-center\">Consent required</h1>");
|
||||||
|
builder.append(" </div>");
|
||||||
|
builder.append(" <div class=\"row\">");
|
||||||
|
builder.append(" <div class=\"col text-center\">");
|
||||||
|
builder.append(" <p><span class=\"font-weight-bold text-primary\">" + registeredClient.getClientId() + "</span> wants to access your account <span class=\"font-weight-bold\">" + authorization.getPrincipalName() + "</span></p>");
|
||||||
|
builder.append(" </div>");
|
||||||
|
builder.append(" </div>");
|
||||||
|
builder.append(" <div class=\"row pb-3\">");
|
||||||
|
builder.append(" <div class=\"col text-center\">");
|
||||||
|
builder.append(" <p>The following permissions are requested by the above app.<br/>Please review these and consent if you approve.</p>");
|
||||||
|
builder.append(" </div>");
|
||||||
|
builder.append(" </div>");
|
||||||
|
builder.append(" <div class=\"row\">");
|
||||||
|
builder.append(" <div class=\"col text-center\">");
|
||||||
|
builder.append(" <form method=\"post\" action=\"" + request.getRequestURI() + "\">");
|
||||||
|
builder.append(" <input type=\"hidden\" name=\"client_id\" value=\"" + registeredClient.getClientId() + "\">");
|
||||||
|
builder.append(" <input type=\"hidden\" name=\"state\" value=\"" + state + "\">");
|
||||||
|
|
||||||
|
for (String scope : authorizationRequest.getScopes()) {
|
||||||
|
builder.append(" <div class=\"form-group form-check py-1\">");
|
||||||
|
builder.append(" <input class=\"form-check-input\" type=\"checkbox\" name=\"scope\" value=\"" + scope + "\" id=\"" + scope + "\" checked>");
|
||||||
|
builder.append(" <label class=\"form-check-label\" for=\"" + scope + "\">" + scope + "</label>");
|
||||||
|
builder.append(" </div>");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append(" <div class=\"form-group pt-3\">");
|
||||||
|
builder.append(" <button class=\"btn btn-primary btn-lg\" type=\"submit\" name=\"consent_action\" value=\"approve\">Submit Consent</button>");
|
||||||
|
builder.append(" </div>");
|
||||||
|
builder.append(" <div class=\"form-group\">");
|
||||||
|
builder.append(" <button class=\"btn btn-link regular\" type=\"submit\" name=\"consent_action\" value=\"cancel\">Cancel</button>");
|
||||||
|
builder.append(" </div>");
|
||||||
|
builder.append(" </form>");
|
||||||
|
builder.append(" </div>");
|
||||||
|
builder.append(" </div>");
|
||||||
|
builder.append(" <div class=\"row pt-4\">");
|
||||||
|
builder.append(" <div class=\"col text-center\">");
|
||||||
|
builder.append(" <p><small>Your consent to provide access is required.<br/>If you do not approve, click Cancel, in which case no information will be shared with the app.</small></p>");
|
||||||
|
builder.append(" </div>");
|
||||||
|
builder.append(" </div>");
|
||||||
|
builder.append("</div>");
|
||||||
|
builder.append("</body>");
|
||||||
|
builder.append("</html>");
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import javax.servlet.ServletException;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@code Filter} that processes an authentication request for an OAuth 2.0 Client.
|
* A {@code Filter} that processes an authentication request for an OAuth 2.0 Client.
|
||||||
@@ -73,7 +74,10 @@ public class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
|
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
|
||||||
this.authenticationManager = authenticationManager;
|
this.authenticationManager = authenticationManager;
|
||||||
this.requestMatcher = requestMatcher;
|
this.requestMatcher = requestMatcher;
|
||||||
this.authenticationConverter = new ClientSecretBasicAuthenticationConverter();
|
this.authenticationConverter = new DelegatingAuthenticationConverter(
|
||||||
|
Arrays.asList(
|
||||||
|
new ClientSecretBasicAuthenticationConverter(),
|
||||||
|
new PublicClientAuthenticationConverter()));
|
||||||
this.authenticationSuccessHandler = this::onAuthenticationSuccess;
|
this.authenticationSuccessHandler = this::onAuthenticationSuccess;
|
||||||
this.authenticationFailureHandler = this::onAuthenticationFailure;
|
this.authenticationFailureHandler = this::onAuthenticationFailure;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.server.authorization.web;
|
package org.springframework.security.oauth2.server.authorization.web;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
@@ -46,4 +49,11 @@ final class OAuth2EndpointUtils {
|
|||||||
});
|
});
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean matchesPkceTokenRequest(HttpServletRequest request) {
|
||||||
|
return AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(
|
||||||
|
request.getParameter(OAuth2ParameterNames.GRANT_TYPE)) &&
|
||||||
|
request.getParameter(OAuth2ParameterNames.CODE) != null &&
|
||||||
|
request.getParameter(PkceParameterNames.CODE_VERIFIER) != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,14 +28,19 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
|||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
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.OAuth2RefreshToken;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
||||||
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
|
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider;
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken;
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationProvider;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationToken;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@@ -54,21 +59,21 @@ import java.util.HashMap;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@code Filter} for the OAuth 2.0 Authorization Code Grant,
|
* A {@code Filter} for the OAuth 2.0 Token endpoint,
|
||||||
* which handles the processing of the OAuth 2.0 Access Token Request.
|
* which handles the processing of an OAuth 2.0 Authorization Grant.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* It converts the OAuth 2.0 Access Token Request to an {@link OAuth2AuthorizationCodeAuthenticationToken},
|
* It converts the OAuth 2.0 Authorization Grant request to an {@link Authentication},
|
||||||
* which is then authenticated by the {@link AuthenticationManager}.
|
* which is then authenticated by the {@link AuthenticationManager}.
|
||||||
* If the authentication succeeds, the {@link AuthenticationManager} returns an
|
* If the authentication succeeds, the {@link AuthenticationManager} returns an
|
||||||
* {@link OAuth2AccessTokenAuthenticationToken}, which contains
|
* {@link OAuth2AccessTokenAuthenticationToken}, which is returned in the OAuth 2.0 Access Token response.
|
||||||
* the {@link OAuth2AccessToken} that is returned in the response.
|
* In case of any error, an {@link OAuth2Error} is returned in the OAuth 2.0 Error response.
|
||||||
* In case of any error, an {@link OAuth2Error} is returned in the response.
|
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* By default, this {@code Filter} responds to access token requests
|
* By default, this {@code Filter} responds to authorization grant requests
|
||||||
* at the {@code URI} {@code /oauth2/token} and {@code HttpMethod} {@code POST}.
|
* at the {@code URI} {@code /oauth2/token} and {@code HttpMethod} {@code POST}.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
@@ -77,11 +82,14 @@ import java.util.Set;
|
|||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @author Madhu Bhat
|
* @author Madhu Bhat
|
||||||
|
* @author Daniel Garnier-Moiroux
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
* @see AuthenticationManager
|
* @see AuthenticationManager
|
||||||
|
* @see OAuth2AuthorizationCodeAuthenticationProvider
|
||||||
|
* @see OAuth2RefreshTokenAuthenticationProvider
|
||||||
|
* @see OAuth2ClientCredentialsAuthenticationProvider
|
||||||
* @see OAuth2AuthorizationService
|
* @see OAuth2AuthorizationService
|
||||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant</a>
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-3.2">Section 3.2 Token Endpoint</a>
|
||||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a>
|
|
||||||
*/
|
*/
|
||||||
public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
|
public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
|
||||||
/**
|
/**
|
||||||
@@ -126,6 +134,7 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
|
|||||||
this.tokenEndpointMatcher = new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name());
|
this.tokenEndpointMatcher = new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name());
|
||||||
Map<AuthorizationGrantType, Converter<HttpServletRequest, Authentication>> converters = new HashMap<>();
|
Map<AuthorizationGrantType, Converter<HttpServletRequest, Authentication>> converters = new HashMap<>();
|
||||||
converters.put(AuthorizationGrantType.AUTHORIZATION_CODE, new AuthorizationCodeAuthenticationConverter());
|
converters.put(AuthorizationGrantType.AUTHORIZATION_CODE, new AuthorizationCodeAuthenticationConverter());
|
||||||
|
converters.put(AuthorizationGrantType.REFRESH_TOKEN, new RefreshTokenAuthenticationConverter());
|
||||||
converters.put(AuthorizationGrantType.CLIENT_CREDENTIALS, new ClientCredentialsAuthenticationConverter());
|
converters.put(AuthorizationGrantType.CLIENT_CREDENTIALS, new ClientCredentialsAuthenticationConverter());
|
||||||
this.authorizationGrantAuthenticationConverter = new DelegatingAuthorizationGrantAuthenticationConverter(converters);
|
this.authorizationGrantAuthenticationConverter = new DelegatingAuthorizationGrantAuthenticationConverter(converters);
|
||||||
}
|
}
|
||||||
@@ -152,7 +161,7 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
||||||
(OAuth2AccessTokenAuthenticationToken) this.authenticationManager.authenticate(authorizationGrantAuthentication);
|
(OAuth2AccessTokenAuthenticationToken) this.authenticationManager.authenticate(authorizationGrantAuthentication);
|
||||||
sendAccessTokenResponse(response, accessTokenAuthentication.getAccessToken());
|
sendAccessTokenResponse(response, accessTokenAuthentication.getAccessToken(), accessTokenAuthentication.getRefreshToken());
|
||||||
|
|
||||||
} catch (OAuth2AuthenticationException ex) {
|
} catch (OAuth2AuthenticationException ex) {
|
||||||
SecurityContextHolder.clearContext();
|
SecurityContextHolder.clearContext();
|
||||||
@@ -160,7 +169,9 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendAccessTokenResponse(HttpServletResponse response, OAuth2AccessToken accessToken) throws IOException {
|
private void sendAccessTokenResponse(HttpServletResponse response, OAuth2AccessToken accessToken,
|
||||||
|
OAuth2RefreshToken refreshToken) throws IOException {
|
||||||
|
|
||||||
OAuth2AccessTokenResponse.Builder builder =
|
OAuth2AccessTokenResponse.Builder builder =
|
||||||
OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
|
OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
|
||||||
.tokenType(accessToken.getTokenType())
|
.tokenType(accessToken.getTokenType())
|
||||||
@@ -168,6 +179,9 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
|
|||||||
if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
|
if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
|
||||||
builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
|
builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
|
||||||
}
|
}
|
||||||
|
if (refreshToken != null) {
|
||||||
|
builder.refreshToken(refreshToken.getTokenValue());
|
||||||
|
}
|
||||||
OAuth2AccessTokenResponse accessTokenResponse = builder.build();
|
OAuth2AccessTokenResponse accessTokenResponse = builder.build();
|
||||||
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
||||||
this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
|
this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
|
||||||
@@ -195,18 +209,9 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
|
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
// client_id (REQUIRED)
|
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
|
||||||
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
|
|
||||||
Authentication clientPrincipal = null;
|
|
||||||
if (StringUtils.hasText(clientId)) {
|
|
||||||
if (parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
|
|
||||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
}
|
|
||||||
|
|
||||||
// code (REQUIRED)
|
// code (REQUIRED)
|
||||||
String code = parameters.getFirst(OAuth2ParameterNames.CODE);
|
String code = parameters.getFirst(OAuth2ParameterNames.CODE);
|
||||||
@@ -223,9 +228,53 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
|
|||||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI);
|
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI);
|
||||||
}
|
}
|
||||||
|
|
||||||
return clientPrincipal != null ?
|
Map<String, Object> additionalParameters = parameters
|
||||||
new OAuth2AuthorizationCodeAuthenticationToken(code, clientPrincipal, redirectUri) :
|
.entrySet()
|
||||||
new OAuth2AuthorizationCodeAuthenticationToken(code, clientId, redirectUri);
|
.stream()
|
||||||
|
.filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE) &&
|
||||||
|
!e.getKey().equals(OAuth2ParameterNames.CLIENT_ID) &&
|
||||||
|
!e.getKey().equals(OAuth2ParameterNames.CODE) &&
|
||||||
|
!e.getKey().equals(OAuth2ParameterNames.REDIRECT_URI))
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
|
||||||
|
|
||||||
|
return new OAuth2AuthorizationCodeAuthenticationToken(code, clientPrincipal, redirectUri, additionalParameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RefreshTokenAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication convert(HttpServletRequest request) {
|
||||||
|
// grant_type (REQUIRED)
|
||||||
|
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
||||||
|
if (!AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(grantType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
|
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
|
||||||
|
|
||||||
|
// refresh_token (REQUIRED)
|
||||||
|
String refreshToken = parameters.getFirst(OAuth2ParameterNames.REFRESH_TOKEN);
|
||||||
|
if (!StringUtils.hasText(refreshToken) ||
|
||||||
|
parameters.get(OAuth2ParameterNames.REFRESH_TOKEN).size() != 1) {
|
||||||
|
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REFRESH_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// scope (OPTIONAL)
|
||||||
|
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
|
||||||
|
if (StringUtils.hasText(scope) &&
|
||||||
|
parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
|
||||||
|
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(scope)) {
|
||||||
|
Set<String> requestedScopes = new HashSet<>(
|
||||||
|
Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
|
||||||
|
return new OAuth2RefreshTokenAuthenticationToken(refreshToken, clientPrincipal, requestedScopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OAuth2RefreshTokenAuthenticationToken(refreshToken, clientPrincipal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.web;
|
||||||
|
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.http.server.ServletServerHttpResponse;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames2;
|
||||||
|
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken;
|
||||||
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code Filter} for the OAuth 2.0 Token Revocation endpoint.
|
||||||
|
*
|
||||||
|
* @author Vivek Babu
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @see OAuth2TokenRevocationAuthenticationProvider
|
||||||
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7009#section-2">Section 2 Token Revocation</a>
|
||||||
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7009#section-2.1">Section 2.1 Revocation Request</a>
|
||||||
|
* @since 0.0.3
|
||||||
|
*/
|
||||||
|
public class OAuth2TokenRevocationEndpointFilter extends OncePerRequestFilter {
|
||||||
|
/**
|
||||||
|
* The default endpoint {@code URI} for token revocation requests.
|
||||||
|
*/
|
||||||
|
public static final String DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI = "/oauth2/revoke";
|
||||||
|
|
||||||
|
private final AuthenticationManager authenticationManager;
|
||||||
|
private final RequestMatcher tokenRevocationEndpointMatcher;
|
||||||
|
private final Converter<HttpServletRequest, Authentication> tokenRevocationAuthenticationConverter =
|
||||||
|
new DefaultTokenRevocationAuthenticationConverter();
|
||||||
|
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
|
||||||
|
new OAuth2ErrorHttpMessageConverter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2TokenRevocationEndpointFilter} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param authenticationManager the authentication manager
|
||||||
|
*/
|
||||||
|
public OAuth2TokenRevocationEndpointFilter(AuthenticationManager authenticationManager) {
|
||||||
|
this(authenticationManager, DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2TokenRevocationEndpointFilter} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param authenticationManager the authentication manager
|
||||||
|
* @param tokenRevocationEndpointUri the endpoint {@code URI} for token revocation requests
|
||||||
|
*/
|
||||||
|
public OAuth2TokenRevocationEndpointFilter(AuthenticationManager authenticationManager,
|
||||||
|
String tokenRevocationEndpointUri) {
|
||||||
|
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
||||||
|
Assert.hasText(tokenRevocationEndpointUri, "tokenRevocationEndpointUri cannot be empty");
|
||||||
|
this.authenticationManager = authenticationManager;
|
||||||
|
this.tokenRevocationEndpointMatcher = new AntPathRequestMatcher(
|
||||||
|
tokenRevocationEndpointUri, HttpMethod.POST.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
|
||||||
|
if (!this.tokenRevocationEndpointMatcher.matches(request)) {
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.authenticationManager.authenticate(
|
||||||
|
this.tokenRevocationAuthenticationConverter.convert(request));
|
||||||
|
response.setStatus(HttpStatus.OK.value());
|
||||||
|
} catch (OAuth2AuthenticationException ex) {
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
sendErrorResponse(response, ex.getError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendErrorResponse(HttpServletResponse response, OAuth2Error error) throws IOException {
|
||||||
|
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
||||||
|
httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
|
||||||
|
this.errorHttpResponseConverter.write(error, null, httpResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void throwError(String errorCode, String parameterName) {
|
||||||
|
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Token Revocation Parameter: " + parameterName,
|
||||||
|
"https://tools.ietf.org/html/rfc7009#section-2.1");
|
||||||
|
throw new OAuth2AuthenticationException(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DefaultTokenRevocationAuthenticationConverter
|
||||||
|
implements Converter<HttpServletRequest, Authentication> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication convert(HttpServletRequest request) {
|
||||||
|
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
|
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
|
||||||
|
|
||||||
|
// token (REQUIRED)
|
||||||
|
String token = parameters.getFirst(OAuth2ParameterNames2.TOKEN);
|
||||||
|
if (!StringUtils.hasText(token) ||
|
||||||
|
parameters.get(OAuth2ParameterNames2.TOKEN).size() != 1) {
|
||||||
|
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames2.TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// token_type_hint (OPTIONAL)
|
||||||
|
String tokenTypeHint = parameters.getFirst(OAuth2ParameterNames2.TOKEN_TYPE_HINT);
|
||||||
|
if (StringUtils.hasText(tokenTypeHint) &&
|
||||||
|
parameters.get(OAuth2ParameterNames2.TOKEN_TYPE_HINT).size() != 1) {
|
||||||
|
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames2.TOKEN_TYPE_HINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OAuth2TokenRevocationAuthenticationToken(token, clientPrincipal, tokenTypeHint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.web;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||||
|
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to extract the parameters from {@link HttpServletRequest}
|
||||||
|
* used for authenticating public clients using Proof Key for Code Exchange (PKCE).
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 0.0.2
|
||||||
|
* @see AuthenticationConverter
|
||||||
|
* @see OAuth2ClientAuthenticationToken
|
||||||
|
* @see OAuth2ClientAuthenticationFilter
|
||||||
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636">Proof Key for Code Exchange by OAuth Public Clients</a>
|
||||||
|
*/
|
||||||
|
public class PublicClientAuthenticationConverter implements AuthenticationConverter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication convert(HttpServletRequest request) {
|
||||||
|
if (!OAuth2EndpointUtils.matchesPkceTokenRequest(request)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
|
||||||
|
|
||||||
|
// client_id (REQUIRED for public clients)
|
||||||
|
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
|
||||||
|
if (!StringUtils.hasText(clientId) ||
|
||||||
|
parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
|
||||||
|
}
|
||||||
|
|
||||||
|
// code_verifier (REQUIRED)
|
||||||
|
if (parameters.get(PkceParameterNames.CODE_VERIFIER).size() != 1) {
|
||||||
|
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters.remove(OAuth2ParameterNames.CLIENT_ID);
|
||||||
|
|
||||||
|
return new OAuth2ClientAuthenticationToken(
|
||||||
|
clientId, new HashMap<>(parameters.toSingleValueMap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.config.annotation.web.configuration;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.OrderUtils;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link OAuth2AuthorizationServerSecurity}.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
public class OAuth2AuthorizationServerSecurityTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void assertOrderHighestPrecedence() {
|
||||||
|
Integer authorizationServerSecurityOrder = OrderUtils.getOrder(OAuth2AuthorizationServerSecurity.class);
|
||||||
|
Integer defaultSecurityOrder = OrderUtils.getOrder(WebSecurityConfigurerAdapter.class);
|
||||||
|
assertThat(authorizationServerSecurityOrder).isNotEqualTo(defaultSecurityOrder);
|
||||||
|
assertThat(authorizationServerSecurityOrder).isEqualTo(Ordered.HIGHEST_PRECEDENCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import org.junit.Before;
|
|||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
@@ -31,14 +32,15 @@ import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
|
|||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||||
import org.springframework.security.oauth2.server.authorization.TokenType;
|
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
|
||||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
|
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
|
||||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
|
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
@@ -58,20 +60,29 @@ import static org.mockito.ArgumentMatchers.any;
|
|||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.reset;
|
import static org.mockito.Mockito.reset;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyNoInteractions;
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
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.header;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests for the OAuth 2.0 Authorization Code Grant.
|
* Integration tests for the OAuth 2.0 Authorization Code Grant.
|
||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
|
* @author Daniel Garnier-Moiroux
|
||||||
*/
|
*/
|
||||||
public class OAuth2AuthorizationCodeGrantTests {
|
public class OAuth2AuthorizationCodeGrantTests {
|
||||||
|
// See RFC 7636: Appendix B. Example for the S256 code_challenge_method
|
||||||
|
// https://tools.ietf.org/html/rfc7636#appendix-B
|
||||||
|
private static final String S256_CODE_VERIFIER = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
|
||||||
|
private static final String S256_CODE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM";
|
||||||
|
|
||||||
private static RegisteredClientRepository registeredClientRepository;
|
private static RegisteredClientRepository registeredClientRepository;
|
||||||
private static OAuth2AuthorizationService authorizationService;
|
private static OAuth2AuthorizationService authorizationService;
|
||||||
private static KeyManager keyManager;
|
private static KeyManager keyManager;
|
||||||
@@ -133,7 +144,7 @@ public class OAuth2AuthorizationCodeGrantTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void requestWhenTokenRequestValidThenResponseIncludesCacheHeaders() throws Exception {
|
public void requestWhenTokenRequestValidThenReturnAccessTokenResponse() throws Exception {
|
||||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||||
|
|
||||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
@@ -142,25 +153,80 @@ public class OAuth2AuthorizationCodeGrantTests {
|
|||||||
|
|
||||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
when(authorizationService.findByToken(
|
when(authorizationService.findByToken(
|
||||||
eq(authorization.getAttribute(OAuth2AuthorizationAttributeNames.CODE)),
|
eq(authorization.getTokens().getToken(OAuth2AuthorizationCode.class).getTokenValue()),
|
||||||
eq(TokenType.AUTHORIZATION_CODE)))
|
eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
.thenReturn(authorization);
|
.thenReturn(authorization);
|
||||||
|
|
||||||
this.mvc.perform(MockMvcRequestBuilders.post(OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI)
|
this.mvc.perform(post(OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI)
|
||||||
.params(getTokenRequestParameters(registeredClient, authorization))
|
.params(getTokenRequestParameters(registeredClient, authorization))
|
||||||
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
|
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
|
||||||
registeredClient.getClientId(), registeredClient.getClientSecret())))
|
registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
|
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
|
||||||
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")));
|
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
|
||||||
|
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.token_type").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.refresh_token").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.scope").isNotEmpty());
|
||||||
|
|
||||||
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
|
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
|
||||||
verify(authorizationService).findByToken(
|
verify(authorizationService).findByToken(
|
||||||
eq(authorization.getAttribute(OAuth2AuthorizationAttributeNames.CODE)),
|
eq(authorization.getTokens().getToken(OAuth2AuthorizationCode.class).getTokenValue()),
|
||||||
eq(TokenType.AUTHORIZATION_CODE));
|
eq(TokenType.AUTHORIZATION_CODE));
|
||||||
verify(authorizationService).save(any());
|
verify(authorizationService).save(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void requestWhenPublicClientWithPkceThenReturnAccessTokenResponse() throws Exception {
|
||||||
|
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||||
|
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||||
|
.clientSecret(null)
|
||||||
|
.clientSettings(clientSettings -> clientSettings.requireProofKey(true))
|
||||||
|
.tokenSettings(tokenSettings -> tokenSettings.enableRefreshTokens(false))
|
||||||
|
.build();
|
||||||
|
when(registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
MvcResult mvcResult = this.mvc.perform(get(OAuth2AuthorizationEndpointFilter.DEFAULT_AUTHORIZATION_ENDPOINT_URI)
|
||||||
|
.params(getAuthorizationRequestParameters(registeredClient))
|
||||||
|
.param(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE)
|
||||||
|
.param(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256")
|
||||||
|
.with(user("user")))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andReturn();
|
||||||
|
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://example.com\\?code=.{15,}&state=state");
|
||||||
|
|
||||||
|
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
|
||||||
|
|
||||||
|
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||||
|
verify(authorizationService).save(authorizationCaptor.capture());
|
||||||
|
OAuth2Authorization authorization = authorizationCaptor.getValue();
|
||||||
|
|
||||||
|
when(authorizationService.findByToken(
|
||||||
|
eq(authorization.getTokens().getToken(OAuth2AuthorizationCode.class).getTokenValue()),
|
||||||
|
eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
this.mvc.perform(post(OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI)
|
||||||
|
.params(getTokenRequestParameters(registeredClient, authorization))
|
||||||
|
.param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
|
||||||
|
.param(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.token_type").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.refresh_token").doesNotExist())
|
||||||
|
.andExpect(jsonPath("$.scope").isNotEmpty());
|
||||||
|
|
||||||
|
verify(registeredClientRepository, times(2)).findByClientId(eq(registeredClient.getClientId()));
|
||||||
|
verify(authorizationService, times(2)).findByToken(
|
||||||
|
eq(authorization.getTokens().getToken(OAuth2AuthorizationCode.class).getTokenValue()),
|
||||||
|
eq(TokenType.AUTHORIZATION_CODE));
|
||||||
|
verify(authorizationService, times(2)).save(any());
|
||||||
|
}
|
||||||
|
|
||||||
private static MultiValueMap<String, String> getAuthorizationRequestParameters(RegisteredClient registeredClient) {
|
private static MultiValueMap<String, String> getAuthorizationRequestParameters(RegisteredClient registeredClient) {
|
||||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||||
parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue());
|
parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue());
|
||||||
@@ -176,7 +242,7 @@ public class OAuth2AuthorizationCodeGrantTests {
|
|||||||
OAuth2Authorization authorization) {
|
OAuth2Authorization authorization) {
|
||||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||||
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
|
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
|
||||||
parameters.set(OAuth2ParameterNames.CODE, authorization.getAttribute(OAuth2AuthorizationAttributeNames.CODE));
|
parameters.set(OAuth2ParameterNames.CODE, authorization.getTokens().getToken(OAuth2AuthorizationCode.class).getTokenValue());
|
||||||
parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next());
|
parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next());
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://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.oauth2.server.authorization;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||||
|
import org.springframework.security.config.test.SpringTestRule;
|
||||||
|
import org.springframework.security.crypto.keys.KeyManager;
|
||||||
|
import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for the OAuth 2.0 Refresh Token Grant.
|
||||||
|
*
|
||||||
|
* @author Alexey Nesterov
|
||||||
|
* @since 0.0.3
|
||||||
|
*/
|
||||||
|
public class OAuth2RefreshTokenGrantTests {
|
||||||
|
private static RegisteredClientRepository registeredClientRepository;
|
||||||
|
private static OAuth2AuthorizationService authorizationService;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final SpringTestRule spring = new SpringTestRule();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mvc;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void init() {
|
||||||
|
registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||||
|
authorizationService = mock(OAuth2AuthorizationService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
reset(registeredClientRepository);
|
||||||
|
reset(authorizationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void requestWhenRefreshTokenRequestValidThenReturnAccessTokenResponse() throws Exception {
|
||||||
|
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||||
|
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
when(authorizationService.findByToken(
|
||||||
|
eq(authorization.getTokens().getRefreshToken().getTokenValue()),
|
||||||
|
eq(TokenType.REFRESH_TOKEN)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
this.mvc.perform(post(OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI)
|
||||||
|
.params(getRefreshTokenRequestParameters(authorization))
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
|
||||||
|
registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
|
||||||
|
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
|
||||||
|
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.token_type").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.refresh_token").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.scope").isNotEmpty());
|
||||||
|
|
||||||
|
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
|
||||||
|
verify(authorizationService).findByToken(
|
||||||
|
eq(authorization.getTokens().getRefreshToken().getTokenValue()),
|
||||||
|
eq(TokenType.REFRESH_TOKEN));
|
||||||
|
verify(authorizationService).save(any());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MultiValueMap<String, String> getRefreshTokenRequestParameters(OAuth2Authorization authorization) {
|
||||||
|
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||||
|
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.REFRESH_TOKEN.getValue());
|
||||||
|
parameters.set(OAuth2ParameterNames.REFRESH_TOKEN, authorization.getTokens().getRefreshToken().getTokenValue());
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String encodeBasicAuth(String clientId, String secret) throws Exception {
|
||||||
|
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name());
|
||||||
|
secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name());
|
||||||
|
String credentialsString = clientId + ":" + secret;
|
||||||
|
byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8));
|
||||||
|
return new String(encodedBytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
@Import(OAuth2AuthorizationServerConfiguration.class)
|
||||||
|
static class AuthorizationServerConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
RegisteredClientRepository registeredClientRepository() {
|
||||||
|
return registeredClientRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
OAuth2AuthorizationService authorizationService() {
|
||||||
|
return authorizationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
KeyManager keyManager() { return new StaticKeyGeneratingKeyManager(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://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.oauth2.server.authorization;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||||
|
import org.springframework.security.config.test.SpringTestRule;
|
||||||
|
import org.springframework.security.crypto.keys.KeyManager;
|
||||||
|
import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
|
||||||
|
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames2;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for the OAuth 2.0 Token Revocation endpoint.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
public class OAuth2TokenRevocationTests {
|
||||||
|
private static RegisteredClientRepository registeredClientRepository;
|
||||||
|
private static OAuth2AuthorizationService authorizationService;
|
||||||
|
private static KeyManager keyManager;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final SpringTestRule spring = new SpringTestRule();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mvc;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void init() {
|
||||||
|
registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||||
|
authorizationService = mock(OAuth2AuthorizationService.class);
|
||||||
|
keyManager = new StaticKeyGeneratingKeyManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
reset(registeredClientRepository);
|
||||||
|
reset(authorizationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void requestWhenRevokeRefreshTokenThenRevoked() throws Exception {
|
||||||
|
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||||
|
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
OAuth2RefreshToken token = authorization.getTokens().getRefreshToken();
|
||||||
|
TokenType tokenType = TokenType.REFRESH_TOKEN;
|
||||||
|
when(authorizationService.findByToken(eq(token.getTokenValue()), eq(tokenType))).thenReturn(authorization);
|
||||||
|
|
||||||
|
this.mvc.perform(MockMvcRequestBuilders.post(OAuth2TokenRevocationEndpointFilter.DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI)
|
||||||
|
.params(getTokenRevocationRequestParameters(token, tokenType))
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
|
||||||
|
registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
|
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
|
||||||
|
verify(authorizationService).findByToken(eq(token.getTokenValue()), eq(tokenType));
|
||||||
|
|
||||||
|
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||||
|
verify(authorizationService).save(authorizationCaptor.capture());
|
||||||
|
|
||||||
|
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||||
|
OAuth2RefreshToken refreshToken = updatedAuthorization.getTokens().getRefreshToken();
|
||||||
|
assertThat(updatedAuthorization.getTokens().getTokenMetadata(refreshToken).isInvalidated()).isTrue();
|
||||||
|
OAuth2AccessToken accessToken = updatedAuthorization.getTokens().getAccessToken();
|
||||||
|
assertThat(updatedAuthorization.getTokens().getTokenMetadata(accessToken).isInvalidated()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void requestWhenRevokeAccessTokenThenRevoked() throws Exception {
|
||||||
|
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||||
|
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
OAuth2AccessToken token = authorization.getTokens().getAccessToken();
|
||||||
|
TokenType tokenType = TokenType.ACCESS_TOKEN;
|
||||||
|
when(authorizationService.findByToken(eq(token.getTokenValue()), eq(tokenType))).thenReturn(authorization);
|
||||||
|
|
||||||
|
this.mvc.perform(MockMvcRequestBuilders.post(OAuth2TokenRevocationEndpointFilter.DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI)
|
||||||
|
.params(getTokenRevocationRequestParameters(token, tokenType))
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
|
||||||
|
registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
|
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
|
||||||
|
verify(authorizationService).findByToken(eq(token.getTokenValue()), eq(tokenType));
|
||||||
|
|
||||||
|
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||||
|
verify(authorizationService).save(authorizationCaptor.capture());
|
||||||
|
|
||||||
|
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||||
|
OAuth2AccessToken accessToken = updatedAuthorization.getTokens().getAccessToken();
|
||||||
|
assertThat(updatedAuthorization.getTokens().getTokenMetadata(accessToken).isInvalidated()).isTrue();
|
||||||
|
OAuth2RefreshToken refreshToken = updatedAuthorization.getTokens().getRefreshToken();
|
||||||
|
assertThat(updatedAuthorization.getTokens().getTokenMetadata(refreshToken).isInvalidated()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MultiValueMap<String, String> getTokenRevocationRequestParameters(AbstractOAuth2Token token, TokenType tokenType) {
|
||||||
|
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||||
|
parameters.set(OAuth2ParameterNames2.TOKEN, token.getTokenValue());
|
||||||
|
parameters.set(OAuth2ParameterNames2.TOKEN_TYPE_HINT, tokenType.getValue());
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String encodeBasicAuth(String clientId, String secret) throws Exception {
|
||||||
|
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name());
|
||||||
|
secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name());
|
||||||
|
String credentialsString = clientId + ":" + secret;
|
||||||
|
byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8));
|
||||||
|
return new String(encodedBytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
@Import(OAuth2AuthorizationServerConfiguration.class)
|
||||||
|
static class AuthorizationServerConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
RegisteredClientRepository registeredClientRepository() {
|
||||||
|
return registeredClientRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
OAuth2AuthorizationService authorizationService() {
|
||||||
|
return authorizationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
KeyManager keyManager() {
|
||||||
|
return keyManager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,10 +18,14 @@ package org.springframework.security.oauth2.server.authorization;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
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;
|
||||||
@@ -30,11 +34,13 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|||||||
* Tests for {@link InMemoryOAuth2AuthorizationService}.
|
* Tests for {@link InMemoryOAuth2AuthorizationService}.
|
||||||
*
|
*
|
||||||
* @author Krisztian Toth
|
* @author Krisztian Toth
|
||||||
|
* @author Joe Grandja
|
||||||
*/
|
*/
|
||||||
public class InMemoryOAuth2AuthorizationServiceTests {
|
public class InMemoryOAuth2AuthorizationServiceTests {
|
||||||
private static final RegisteredClient REGISTERED_CLIENT = TestRegisteredClients.registeredClient().build();
|
private static final RegisteredClient REGISTERED_CLIENT = TestRegisteredClients.registeredClient().build();
|
||||||
private static final String PRINCIPAL_NAME = "principal";
|
private static final String PRINCIPAL_NAME = "principal";
|
||||||
private static final String AUTHORIZATION_CODE = "code";
|
private static final OAuth2AuthorizationCode AUTHORIZATION_CODE = new OAuth2AuthorizationCode(
|
||||||
|
"code", Instant.now(), Instant.now().plus(5, ChronoUnit.MINUTES));
|
||||||
private InMemoryOAuth2AuthorizationService authorizationService;
|
private InMemoryOAuth2AuthorizationService authorizationService;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -53,43 +59,81 @@ public class InMemoryOAuth2AuthorizationServiceTests {
|
|||||||
public void saveWhenAuthorizationProvidedThenSaved() {
|
public void saveWhenAuthorizationProvidedThenSaved() {
|
||||||
OAuth2Authorization expectedAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
OAuth2Authorization expectedAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
||||||
.principalName(PRINCIPAL_NAME)
|
.principalName(PRINCIPAL_NAME)
|
||||||
.attribute(OAuth2AuthorizationAttributeNames.CODE, AUTHORIZATION_CODE)
|
.tokens(OAuth2Tokens.builder().token(AUTHORIZATION_CODE).build())
|
||||||
.build();
|
.build();
|
||||||
this.authorizationService.save(expectedAuthorization);
|
this.authorizationService.save(expectedAuthorization);
|
||||||
|
|
||||||
OAuth2Authorization authorization = this.authorizationService.findByToken(
|
OAuth2Authorization authorization = this.authorizationService.findByToken(
|
||||||
AUTHORIZATION_CODE, TokenType.AUTHORIZATION_CODE);
|
AUTHORIZATION_CODE.getTokenValue(), TokenType.AUTHORIZATION_CODE);
|
||||||
assertThat(authorization).isEqualTo(expectedAuthorization);
|
assertThat(authorization).isEqualTo(expectedAuthorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void findByTokenAndTokenTypeWhenTokenNullThenThrowIllegalArgumentException() {
|
public void removeWhenAuthorizationNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> this.authorizationService.remove(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("authorization cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeWhenAuthorizationProvidedThenRemoved() {
|
||||||
|
OAuth2Authorization expectedAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
||||||
|
.principalName(PRINCIPAL_NAME)
|
||||||
|
.tokens(OAuth2Tokens.builder().token(AUTHORIZATION_CODE).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.authorizationService.save(expectedAuthorization);
|
||||||
|
OAuth2Authorization authorization = this.authorizationService.findByToken(
|
||||||
|
AUTHORIZATION_CODE.getTokenValue(), TokenType.AUTHORIZATION_CODE);
|
||||||
|
assertThat(authorization).isEqualTo(expectedAuthorization);
|
||||||
|
|
||||||
|
this.authorizationService.remove(expectedAuthorization);
|
||||||
|
authorization = this.authorizationService.findByToken(
|
||||||
|
AUTHORIZATION_CODE.getTokenValue(), TokenType.AUTHORIZATION_CODE);
|
||||||
|
assertThat(authorization).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findByTokenWhenTokenNullThenThrowIllegalArgumentException() {
|
||||||
assertThatThrownBy(() -> this.authorizationService.findByToken(null, TokenType.AUTHORIZATION_CODE))
|
assertThatThrownBy(() -> this.authorizationService.findByToken(null, TokenType.AUTHORIZATION_CODE))
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
.hasMessage("token cannot be empty");
|
.hasMessage("token cannot be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void findByTokenAndTokenTypeWhenTokenTypeAuthorizationCodeThenFound() {
|
public void findByTokenWhenTokenTypeStateThenFound() {
|
||||||
|
String state = "state";
|
||||||
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
||||||
.principalName(PRINCIPAL_NAME)
|
.principalName(PRINCIPAL_NAME)
|
||||||
.attribute(OAuth2AuthorizationAttributeNames.CODE, AUTHORIZATION_CODE)
|
.attribute(OAuth2AuthorizationAttributeNames.STATE, state)
|
||||||
.build();
|
.build();
|
||||||
this.authorizationService.save(authorization);
|
this.authorizationService.save(authorization);
|
||||||
|
|
||||||
OAuth2Authorization result = this.authorizationService.findByToken(
|
OAuth2Authorization result = this.authorizationService.findByToken(
|
||||||
AUTHORIZATION_CODE, TokenType.AUTHORIZATION_CODE);
|
state, new TokenType(OAuth2AuthorizationAttributeNames.STATE));
|
||||||
assertThat(authorization).isEqualTo(result);
|
assertThat(authorization).isEqualTo(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void findByTokenAndTokenTypeWhenTokenTypeAccessTokenThenFound() {
|
public void findByTokenWhenTokenTypeAuthorizationCodeThenFound() {
|
||||||
|
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
||||||
|
.principalName(PRINCIPAL_NAME)
|
||||||
|
.tokens(OAuth2Tokens.builder().token(AUTHORIZATION_CODE).build())
|
||||||
|
.build();
|
||||||
|
this.authorizationService.save(authorization);
|
||||||
|
|
||||||
|
OAuth2Authorization result = this.authorizationService.findByToken(
|
||||||
|
AUTHORIZATION_CODE.getTokenValue(), TokenType.AUTHORIZATION_CODE);
|
||||||
|
assertThat(authorization).isEqualTo(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findByTokenWhenTokenTypeAccessTokenThenFound() {
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||||
"access-token", Instant.now().minusSeconds(60), Instant.now());
|
"access-token", Instant.now().minusSeconds(60), Instant.now());
|
||||||
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
||||||
.principalName(PRINCIPAL_NAME)
|
.principalName(PRINCIPAL_NAME)
|
||||||
.attribute(OAuth2AuthorizationAttributeNames.CODE, AUTHORIZATION_CODE)
|
.tokens(OAuth2Tokens.builder().token(AUTHORIZATION_CODE).accessToken(accessToken).build())
|
||||||
.accessToken(accessToken)
|
|
||||||
.build();
|
.build();
|
||||||
this.authorizationService.save(authorization);
|
this.authorizationService.save(authorization);
|
||||||
|
|
||||||
@@ -99,7 +143,21 @@ public class InMemoryOAuth2AuthorizationServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void findByTokenAndTokenTypeWhenTokenDoesNotExistThenNull() {
|
public void findByTokenWhenTokenTypeRefreshTokenThenFound() {
|
||||||
|
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now());
|
||||||
|
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
||||||
|
.principalName(PRINCIPAL_NAME)
|
||||||
|
.tokens(OAuth2Tokens.builder().refreshToken(refreshToken).build())
|
||||||
|
.build();
|
||||||
|
this.authorizationService.save(authorization);
|
||||||
|
|
||||||
|
OAuth2Authorization result = this.authorizationService.findByToken(
|
||||||
|
refreshToken.getTokenValue(), TokenType.REFRESH_TOKEN);
|
||||||
|
assertThat(authorization).isEqualTo(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findByTokenWhenTokenDoesNotExistThenNull() {
|
||||||
OAuth2Authorization result = this.authorizationService.findByToken(
|
OAuth2Authorization result = this.authorizationService.findByToken(
|
||||||
"access-token", TokenType.ACCESS_TOKEN);
|
"access-token", TokenType.ACCESS_TOKEN);
|
||||||
assertThat(result).isNull();
|
assertThat(result).isNull();
|
||||||
|
|||||||
@@ -17,14 +17,17 @@ package org.springframework.security.oauth2.server.authorization;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
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 static org.assertj.core.data.MapEntry.entry;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link OAuth2Authorization}.
|
* Tests for {@link OAuth2Authorization}.
|
||||||
@@ -37,7 +40,9 @@ public class OAuth2AuthorizationTests {
|
|||||||
private static final String PRINCIPAL_NAME = "principal";
|
private static final String PRINCIPAL_NAME = "principal";
|
||||||
private static final OAuth2AccessToken ACCESS_TOKEN = new OAuth2AccessToken(
|
private static final OAuth2AccessToken ACCESS_TOKEN = new OAuth2AccessToken(
|
||||||
OAuth2AccessToken.TokenType.BEARER, "access-token", Instant.now(), Instant.now().plusSeconds(300));
|
OAuth2AccessToken.TokenType.BEARER, "access-token", Instant.now(), Instant.now().plusSeconds(300));
|
||||||
private static final String AUTHORIZATION_CODE = "code";
|
private static final OAuth2RefreshToken REFRESH_TOKEN = new OAuth2RefreshToken("refresh-token", Instant.now());
|
||||||
|
private static final OAuth2AuthorizationCode AUTHORIZATION_CODE = new OAuth2AuthorizationCode(
|
||||||
|
"code", Instant.now(), Instant.now().plus(5, ChronoUnit.MINUTES));
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void withRegisteredClientWhenRegisteredClientNullThenThrowIllegalArgumentException() {
|
public void withRegisteredClientWhenRegisteredClientNullThenThrowIllegalArgumentException() {
|
||||||
@@ -57,14 +62,15 @@ public class OAuth2AuthorizationTests {
|
|||||||
public void fromWhenAuthorizationProvidedThenCopied() {
|
public void fromWhenAuthorizationProvidedThenCopied() {
|
||||||
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
||||||
.principalName(PRINCIPAL_NAME)
|
.principalName(PRINCIPAL_NAME)
|
||||||
.accessToken(ACCESS_TOKEN)
|
.tokens(OAuth2Tokens.builder().token(AUTHORIZATION_CODE).accessToken(ACCESS_TOKEN).build())
|
||||||
.attribute(OAuth2AuthorizationAttributeNames.CODE, AUTHORIZATION_CODE)
|
|
||||||
.build();
|
.build();
|
||||||
OAuth2Authorization authorizationResult = OAuth2Authorization.from(authorization).build();
|
OAuth2Authorization authorizationResult = OAuth2Authorization.from(authorization).build();
|
||||||
|
|
||||||
assertThat(authorizationResult.getRegisteredClientId()).isEqualTo(authorization.getRegisteredClientId());
|
assertThat(authorizationResult.getRegisteredClientId()).isEqualTo(authorization.getRegisteredClientId());
|
||||||
assertThat(authorizationResult.getPrincipalName()).isEqualTo(authorization.getPrincipalName());
|
assertThat(authorizationResult.getPrincipalName()).isEqualTo(authorization.getPrincipalName());
|
||||||
assertThat(authorizationResult.getAccessToken()).isEqualTo(authorization.getAccessToken());
|
assertThat(authorizationResult.getTokens().getAccessToken()).isEqualTo(authorization.getTokens().getAccessToken());
|
||||||
|
assertThat(authorizationResult.getTokens().getToken(OAuth2AuthorizationCode.class))
|
||||||
|
.isEqualTo(authorization.getTokens().getToken(OAuth2AuthorizationCode.class));
|
||||||
assertThat(authorizationResult.getAttributes()).isEqualTo(authorization.getAttributes());
|
assertThat(authorizationResult.getAttributes()).isEqualTo(authorization.getAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,14 +103,13 @@ public class OAuth2AuthorizationTests {
|
|||||||
public void buildWhenAllAttributesAreProvidedThenAllAttributesAreSet() {
|
public void buildWhenAllAttributesAreProvidedThenAllAttributesAreSet() {
|
||||||
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
|
||||||
.principalName(PRINCIPAL_NAME)
|
.principalName(PRINCIPAL_NAME)
|
||||||
.accessToken(ACCESS_TOKEN)
|
.tokens(OAuth2Tokens.builder().token(AUTHORIZATION_CODE).accessToken(ACCESS_TOKEN).refreshToken(REFRESH_TOKEN).build())
|
||||||
.attribute(OAuth2AuthorizationAttributeNames.CODE, AUTHORIZATION_CODE)
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assertThat(authorization.getRegisteredClientId()).isEqualTo(REGISTERED_CLIENT.getId());
|
assertThat(authorization.getRegisteredClientId()).isEqualTo(REGISTERED_CLIENT.getId());
|
||||||
assertThat(authorization.getPrincipalName()).isEqualTo(PRINCIPAL_NAME);
|
assertThat(authorization.getPrincipalName()).isEqualTo(PRINCIPAL_NAME);
|
||||||
assertThat(authorization.getAccessToken()).isEqualTo(ACCESS_TOKEN);
|
assertThat(authorization.getTokens().getToken(OAuth2AuthorizationCode.class)).isEqualTo(AUTHORIZATION_CODE);
|
||||||
assertThat(authorization.getAttributes()).containsExactly(
|
assertThat(authorization.getTokens().getAccessToken()).isEqualTo(ACCESS_TOKEN);
|
||||||
entry(OAuth2AuthorizationAttributeNames.CODE, AUTHORIZATION_CODE));
|
assertThat(authorization.getTokens().getRefreshToken()).isEqualTo(REFRESH_TOKEN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,22 @@
|
|||||||
package org.springframework.security.oauth2.server.authorization;
|
package org.springframework.security.oauth2.server.authorization;
|
||||||
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
|
* @author Daniel Garnier-Moiroux
|
||||||
*/
|
*/
|
||||||
public class TestOAuth2Authorizations {
|
public class TestOAuth2Authorizations {
|
||||||
|
|
||||||
@@ -32,18 +40,29 @@ public class TestOAuth2Authorizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient) {
|
public static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient) {
|
||||||
|
return authorization(registeredClient, Collections.emptyMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient,
|
||||||
|
Map<String, Object> authorizationRequestAdditionalParameters) {
|
||||||
|
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
|
||||||
|
"code", Instant.now(), Instant.now().plusSeconds(120));
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||||
OAuth2AccessToken.TokenType.BEARER, "access-token", Instant.now(), Instant.now().plusSeconds(300));
|
OAuth2AccessToken.TokenType.BEARER, "access-token", Instant.now(), Instant.now().plusSeconds(300));
|
||||||
|
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken2(
|
||||||
|
"refresh-token", Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS));
|
||||||
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||||
.authorizationUri("https://provider.com/oauth2/authorize")
|
.authorizationUri("https://provider.com/oauth2/authorize")
|
||||||
.clientId(registeredClient.getClientId())
|
.clientId(registeredClient.getClientId())
|
||||||
.redirectUri(registeredClient.getRedirectUris().iterator().next())
|
.redirectUri(registeredClient.getRedirectUris().iterator().next())
|
||||||
|
.scopes(registeredClient.getScopes())
|
||||||
|
.additionalParameters(authorizationRequestAdditionalParameters)
|
||||||
.state("state")
|
.state("state")
|
||||||
.build();
|
.build();
|
||||||
return OAuth2Authorization.withRegisteredClient(registeredClient)
|
return OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||||
.principalName("principal")
|
.principalName("principal")
|
||||||
.accessToken(accessToken)
|
.tokens(OAuth2Tokens.builder().token(authorizationCode).accessToken(accessToken).refreshToken(refreshToken).build())
|
||||||
.attribute(OAuth2AuthorizationAttributeNames.CODE, "code")
|
.attribute(OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST, authorizationRequest)
|
||||||
.attribute(OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST, authorizationRequest);
|
.attribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES, authorizationRequest.getScopes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,22 +22,28 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
|
|||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.security.oauth2.jose.JoseHeaderNames;
|
import org.springframework.security.oauth2.jose.JoseHeaderNames;
|
||||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||||
import org.springframework.security.oauth2.server.authorization.TokenType;
|
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenMetadata;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
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;
|
||||||
@@ -51,9 +57,10 @@ import static org.mockito.Mockito.when;
|
|||||||
* Tests for {@link OAuth2AuthorizationCodeAuthenticationProvider}.
|
* Tests for {@link OAuth2AuthorizationCodeAuthenticationProvider}.
|
||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
|
* @author Daniel Garnier-Moiroux
|
||||||
*/
|
*/
|
||||||
public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||||
private RegisteredClient registeredClient;
|
private static final String AUTHORIZATION_CODE = "code";
|
||||||
private RegisteredClientRepository registeredClientRepository;
|
private RegisteredClientRepository registeredClientRepository;
|
||||||
private OAuth2AuthorizationService authorizationService;
|
private OAuth2AuthorizationService authorizationService;
|
||||||
private JwtEncoder jwtEncoder;
|
private JwtEncoder jwtEncoder;
|
||||||
@@ -61,8 +68,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
this.registeredClient = TestRegisteredClients.registeredClient().build();
|
this.registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||||
this.registeredClientRepository = new InMemoryRegisteredClientRepository(this.registeredClient);
|
|
||||||
this.authorizationService = mock(OAuth2AuthorizationService.class);
|
this.authorizationService = mock(OAuth2AuthorizationService.class);
|
||||||
this.jwtEncoder = mock(JwtEncoder.class);
|
this.jwtEncoder = mock(JwtEncoder.class);
|
||||||
this.authenticationProvider = new OAuth2AuthorizationCodeAuthenticationProvider(
|
this.authenticationProvider = new OAuth2AuthorizationCodeAuthenticationProvider(
|
||||||
@@ -97,10 +103,11 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() {
|
public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(
|
TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(
|
||||||
this.registeredClient.getClientId(), this.registeredClient.getClientSecret());
|
registeredClient.getClientId(), registeredClient.getClientSecret());
|
||||||
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||||
new OAuth2AuthorizationCodeAuthenticationToken("code", clientPrincipal, null);
|
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, null, null);
|
||||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
@@ -110,10 +117,11 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
|
public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||||
this.registeredClient.getClientId(), this.registeredClient.getClientSecret());
|
registeredClient.getClientId(), registeredClient.getClientSecret(), null);
|
||||||
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||||
new OAuth2AuthorizationCodeAuthenticationToken("code", clientPrincipal, null);
|
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, null, null);
|
||||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
@@ -123,9 +131,10 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenInvalidCodeThenThrowOAuth2AuthenticationException() {
|
public void authenticateWhenInvalidCodeThenThrowOAuth2AuthenticationException() {
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient);
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||||
new OAuth2AuthorizationCodeAuthenticationToken("code", clientPrincipal, null);
|
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, null, null);
|
||||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
@@ -136,13 +145,38 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
|||||||
@Test
|
@Test
|
||||||
public void authenticateWhenCodeIssuedToAnotherClientThenThrowOAuth2AuthenticationException() {
|
public void authenticateWhenCodeIssuedToAnotherClientThenThrowOAuth2AuthenticationException() {
|
||||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization().build();
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization().build();
|
||||||
when(this.authorizationService.findByToken(eq("code"), eq(TokenType.AUTHORIZATION_CODE)))
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
.thenReturn(authorization);
|
.thenReturn(authorization);
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||||
TestRegisteredClients.registeredClient2().build());
|
TestRegisteredClients.registeredClient2().build());
|
||||||
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||||
new OAuth2AuthorizationCodeAuthenticationToken("code", clientPrincipal, null);
|
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, null, null);
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
|
||||||
|
|
||||||
|
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||||
|
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||||
|
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||||
|
OAuth2AuthorizationCode authorizationCode = updatedAuthorization.getTokens().getToken(OAuth2AuthorizationCode.class);
|
||||||
|
assertThat(updatedAuthorization.getTokens().getTokenMetadata(authorizationCode).isInvalidated()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenInvalidRedirectUriThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||||
|
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
|
||||||
|
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||||
|
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri() + "-invalid", null);
|
||||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
@@ -151,16 +185,24 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenInvalidRedirectUriThenThrowOAuth2AuthenticationException() {
|
public void authenticateWhenInvalidatedCodeThenThrowOAuth2AuthenticationException() {
|
||||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization().build();
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
when(this.authorizationService.findByToken(eq("code"), eq(TokenType.AUTHORIZATION_CODE)))
|
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
|
||||||
|
AUTHORIZATION_CODE, Instant.now(), Instant.now().plusSeconds(120));
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||||
|
.tokens(OAuth2Tokens.builder()
|
||||||
|
.token(authorizationCode, OAuth2TokenMetadata.builder().invalidated().build())
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
.thenReturn(authorization);
|
.thenReturn(authorization);
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient);
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||||
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
|
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
|
||||||
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||||
new OAuth2AuthorizationCodeAuthenticationToken("code", clientPrincipal, authorizationRequest.getRedirectUri() + "-invalid");
|
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
|
||||||
|
|
||||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
@@ -170,24 +212,61 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenValidCodeThenReturnAccessToken() {
|
public void authenticateWhenValidCodeThenReturnAccessToken() {
|
||||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization().build();
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
when(this.authorizationService.findByToken(eq("code"), eq(TokenType.AUTHORIZATION_CODE)))
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
.thenReturn(authorization);
|
.thenReturn(authorization);
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient);
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||||
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
|
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
|
||||||
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||||
new OAuth2AuthorizationCodeAuthenticationToken("code", clientPrincipal, authorizationRequest.getRedirectUri());
|
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
|
||||||
|
|
||||||
Instant issuedAt = Instant.now();
|
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
|
||||||
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);
|
|
||||||
Jwt jwt = Jwt.withTokenValue("token")
|
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
||||||
.header(JoseHeaderNames.ALG, SignatureAlgorithm.RS256.getName())
|
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
.issuedAt(issuedAt)
|
|
||||||
.expiresAt(expiresAt)
|
ArgumentCaptor<JwtClaimsSet> jwtClaimsSetCaptor = ArgumentCaptor.forClass(JwtClaimsSet.class);
|
||||||
|
verify(this.jwtEncoder).encode(any(), jwtClaimsSetCaptor.capture());
|
||||||
|
JwtClaimsSet jwtClaimsSet = jwtClaimsSetCaptor.getValue();
|
||||||
|
|
||||||
|
Set<String> scopes = jwtClaimsSet.getClaim(OAuth2ParameterNames.SCOPE);
|
||||||
|
assertThat(scopes).isEqualTo(authorization.getAttribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES));
|
||||||
|
assertThat(jwtClaimsSet.getSubject()).isEqualTo(authorization.getPrincipalName());
|
||||||
|
|
||||||
|
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||||
|
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||||
|
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||||
|
|
||||||
|
assertThat(accessTokenAuthentication.getRegisteredClient().getId()).isEqualTo(updatedAuthorization.getRegisteredClientId());
|
||||||
|
assertThat(accessTokenAuthentication.getPrincipal()).isEqualTo(clientPrincipal);
|
||||||
|
assertThat(accessTokenAuthentication.getAccessToken()).isEqualTo(updatedAuthorization.getTokens().getAccessToken());
|
||||||
|
assertThat(accessTokenAuthentication.getRefreshToken()).isNotNull();
|
||||||
|
assertThat(accessTokenAuthentication.getRefreshToken()).isEqualTo(updatedAuthorization.getTokens().getRefreshToken());
|
||||||
|
OAuth2AuthorizationCode authorizationCode = updatedAuthorization.getTokens().getToken(OAuth2AuthorizationCode.class);
|
||||||
|
assertThat(updatedAuthorization.getTokens().getTokenMetadata(authorizationCode).isInvalidated()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenRefreshTokenTimeToLiveConfiguredThenRefreshTokenExpirySet() {
|
||||||
|
Duration refreshTokenTTL = Duration.ofDays(1);
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||||
|
.tokenSettings(tokenSettings -> tokenSettings.refreshTokenTimeToLive(refreshTokenTTL))
|
||||||
.build();
|
.build();
|
||||||
when(this.jwtEncoder.encode(any(), any())).thenReturn(jwt);
|
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||||
|
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
|
||||||
|
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||||
|
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
|
||||||
|
|
||||||
|
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
|
||||||
|
|
||||||
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
||||||
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
@@ -196,9 +275,43 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
|||||||
verify(this.authorizationService).save(authorizationCaptor.capture());
|
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||||
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||||
|
|
||||||
assertThat(accessTokenAuthentication.getRegisteredClient().getId()).isEqualTo(updatedAuthorization.getRegisteredClientId());
|
assertThat(accessTokenAuthentication.getRefreshToken()).isEqualTo(updatedAuthorization.getTokens().getRefreshToken());
|
||||||
assertThat(accessTokenAuthentication.getPrincipal()).isEqualTo(clientPrincipal);
|
Instant expectedRefreshTokenExpiresAt = accessTokenAuthentication.getRefreshToken().getIssuedAt().plus(refreshTokenTTL);
|
||||||
assertThat(updatedAuthorization.getAccessToken()).isNotNull();
|
assertThat(accessTokenAuthentication.getRefreshToken().getExpiresAt()).isBetween(
|
||||||
assertThat(accessTokenAuthentication.getAccessToken()).isEqualTo(updatedAuthorization.getAccessToken());
|
expectedRefreshTokenExpiresAt.minusSeconds(1), expectedRefreshTokenExpiresAt.plusSeconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenRefreshTokenDisabledThenRefreshTokenNull() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||||
|
.tokenSettings(tokenSettings -> tokenSettings.enableRefreshTokens(false))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||||
|
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
|
||||||
|
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||||
|
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
|
||||||
|
|
||||||
|
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
|
||||||
|
|
||||||
|
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
||||||
|
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
|
|
||||||
|
assertThat(accessTokenAuthentication.getRefreshToken()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Jwt createJwt() {
|
||||||
|
Instant issuedAt = Instant.now();
|
||||||
|
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);
|
||||||
|
return Jwt.withTokenValue("token")
|
||||||
|
.header(JoseHeaderNames.ALG, SignatureAlgorithm.RS256.getName())
|
||||||
|
.issuedAt(issuedAt)
|
||||||
|
.expiresAt(expiresAt)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,11 @@
|
|||||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@@ -26,52 +28,49 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|||||||
* Tests for {@link OAuth2AuthorizationCodeAuthenticationToken}.
|
* Tests for {@link OAuth2AuthorizationCodeAuthenticationToken}.
|
||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
|
* @author Daniel Garnier-Moiroux
|
||||||
*/
|
*/
|
||||||
public class OAuth2AuthorizationCodeAuthenticationTokenTests {
|
public class OAuth2AuthorizationCodeAuthenticationTokenTests {
|
||||||
private String code = "code";
|
private String code = "code";
|
||||||
private OAuth2ClientAuthenticationToken clientPrincipal =
|
private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||||
new OAuth2ClientAuthenticationToken(TestRegisteredClients.registeredClient().build());
|
TestRegisteredClients.registeredClient().build());
|
||||||
private String clientId = "clientId";
|
|
||||||
private String redirectUri = "redirectUri";
|
private String redirectUri = "redirectUri";
|
||||||
|
private Map<String, Object> additionalParameters = Collections.singletonMap("param1", "value1");
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenCodeNullThenThrowIllegalArgumentException() {
|
public void constructorWhenCodeNullThenThrowIllegalArgumentException() {
|
||||||
assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(null, this.clientPrincipal, this.redirectUri))
|
assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(null, this.clientPrincipal, this.redirectUri, null))
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
.hasMessage("code cannot be empty");
|
.hasMessage("code cannot be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() {
|
public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() {
|
||||||
assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(this.code, (Authentication) null, this.redirectUri))
|
assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(this.code, null, this.redirectUri, null))
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
.hasMessage("clientPrincipal cannot be null");
|
.hasMessage("clientPrincipal cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void constructorWhenClientIdNullThenThrowIllegalArgumentException() {
|
|
||||||
assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(this.code, (String) null, this.redirectUri))
|
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
|
||||||
.hasMessage("clientId cannot be empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenClientPrincipalProvidedThenCreated() {
|
public void constructorWhenClientPrincipalProvidedThenCreated() {
|
||||||
OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken(
|
OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken(
|
||||||
this.code, this.clientPrincipal, this.redirectUri);
|
this.code, this.clientPrincipal, this.redirectUri, this.additionalParameters);
|
||||||
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
|
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
|
||||||
assertThat(authentication.getCredentials().toString()).isEmpty();
|
assertThat(authentication.getCredentials().toString()).isEmpty();
|
||||||
assertThat(authentication.getCode()).isEqualTo(this.code);
|
assertThat(authentication.getCode()).isEqualTo(this.code);
|
||||||
assertThat(authentication.getRedirectUri()).isEqualTo(this.redirectUri);
|
assertThat(authentication.getRedirectUri()).isEqualTo(this.redirectUri);
|
||||||
|
assertThat(authentication.getAdditionalParameters()).isEqualTo(this.additionalParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenClientIdProvidedThenCreated() {
|
public void getAdditionalParametersWhenUpdateThenThrowUnsupportedOperationException() {
|
||||||
OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken(
|
OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken(
|
||||||
this.code, this.clientId, this.redirectUri);
|
this.code, this.clientPrincipal, this.redirectUri, this.additionalParameters);
|
||||||
assertThat(authentication.getPrincipal()).isEqualTo(this.clientId);
|
assertThatThrownBy(() -> authentication.getAdditionalParameters().put("another_key", 1))
|
||||||
assertThat(authentication.getCredentials().toString()).isEmpty();
|
.isInstanceOf(UnsupportedOperationException.class);
|
||||||
assertThat(authentication.getCode()).isEqualTo(this.code);
|
assertThatThrownBy(() -> authentication.getAdditionalParameters().remove("some_key"))
|
||||||
assertThat(authentication.getRedirectUri()).isEqualTo(this.redirectUri);
|
.isInstanceOf(UnsupportedOperationException.class);
|
||||||
|
assertThatThrownBy(() -> authentication.getAdditionalParameters().clear())
|
||||||
|
.isInstanceOf(UnsupportedOperationException.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,41 +17,72 @@ package org.springframework.security.oauth2.server.authorization.authentication;
|
|||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
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 static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link OAuth2ClientAuthenticationProvider}.
|
* Tests for {@link OAuth2ClientAuthenticationProvider}.
|
||||||
*
|
*
|
||||||
* @author Patryk Kostrzewa
|
* @author Patryk Kostrzewa
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
|
* @author Daniel Garnier-Moiroux
|
||||||
*/
|
*/
|
||||||
public class OAuth2ClientAuthenticationProviderTests {
|
public class OAuth2ClientAuthenticationProviderTests {
|
||||||
private RegisteredClient registeredClient;
|
private static final String PLAIN_CODE_VERIFIER = "pkce-key";
|
||||||
|
private static final String PLAIN_CODE_CHALLENGE = PLAIN_CODE_VERIFIER;
|
||||||
|
|
||||||
|
// See RFC 7636: Appendix B. Example for the S256 code_challenge_method
|
||||||
|
// https://tools.ietf.org/html/rfc7636#appendix-B
|
||||||
|
private static final String S256_CODE_VERIFIER = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
|
||||||
|
private static final String S256_CODE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM";
|
||||||
|
|
||||||
|
private static final String AUTHORIZATION_CODE = "code";
|
||||||
|
|
||||||
private RegisteredClientRepository registeredClientRepository;
|
private RegisteredClientRepository registeredClientRepository;
|
||||||
|
private OAuth2AuthorizationService authorizationService;
|
||||||
private OAuth2ClientAuthenticationProvider authenticationProvider;
|
private OAuth2ClientAuthenticationProvider authenticationProvider;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
this.registeredClient = TestRegisteredClients.registeredClient().build();
|
this.registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||||
this.registeredClientRepository = new InMemoryRegisteredClientRepository(this.registeredClient);
|
this.authorizationService = mock(OAuth2AuthorizationService.class);
|
||||||
this.authenticationProvider = new OAuth2ClientAuthenticationProvider(this.registeredClientRepository);
|
this.authenticationProvider = new OAuth2ClientAuthenticationProvider(
|
||||||
|
this.registeredClientRepository, this.authorizationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
|
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
|
||||||
assertThatThrownBy(() -> new OAuth2ClientAuthenticationProvider(null))
|
assertThatThrownBy(() -> new OAuth2ClientAuthenticationProvider(null, this.authorizationService))
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
.hasMessage("registeredClientRepository cannot be null");
|
.hasMessage("registeredClientRepository cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2ClientAuthenticationProvider(this.registeredClientRepository, null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("authorizationService cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void supportsWhenTypeOAuth2ClientAuthenticationTokenThenReturnTrue() {
|
public void supportsWhenTypeOAuth2ClientAuthenticationTokenThenReturnTrue() {
|
||||||
assertThat(this.authenticationProvider.supports(OAuth2ClientAuthenticationToken.class)).isTrue();
|
assertThat(this.authenticationProvider.supports(OAuth2ClientAuthenticationToken.class)).isTrue();
|
||||||
@@ -59,8 +90,12 @@ public class OAuth2ClientAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthenticationException() {
|
public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||||
this.registeredClient.getClientId() + "-invalid", this.registeredClient.getClientSecret());
|
registeredClient.getClientId() + "-invalid", registeredClient.getClientSecret(), null);
|
||||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
@@ -70,8 +105,27 @@ public class OAuth2ClientAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenInvalidClientSecretThenThrowOAuth2AuthenticationException() {
|
public void authenticateWhenInvalidClientSecretThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||||
this.registeredClient.getClientId(), this.registeredClient.getClientSecret() + "-invalid");
|
registeredClient.getClientId(), registeredClient.getClientSecret() + "-invalid", null);
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenClientSecretNotProvidedThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken authentication =
|
||||||
|
new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), null);
|
||||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
@@ -81,13 +135,267 @@ public class OAuth2ClientAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenValidCredentialsThenAuthenticated() {
|
public void authenticateWhenValidCredentialsThenAuthenticated() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||||
this.registeredClient.getClientId(), this.registeredClient.getClientSecret());
|
registeredClient.getClientId(), registeredClient.getClientSecret(), null);
|
||||||
OAuth2ClientAuthenticationToken authenticationResult =
|
OAuth2ClientAuthenticationToken authenticationResult =
|
||||||
(OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
(OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||||
assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(this.registeredClient.getClientId());
|
assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId());
|
||||||
assertThat(authenticationResult.getCredentials()).isNull();
|
assertThat(authenticationResult.getCredentials()).isNull();
|
||||||
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(this.registeredClient);
|
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenPkceAndInvalidCodeThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||||
|
.authorization(registeredClient, createPkceAuthorizationParametersPlain())
|
||||||
|
.build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
Map<String, Object> parameters = createPkceTokenParameters(PLAIN_CODE_VERIFIER);
|
||||||
|
parameters.put(OAuth2ParameterNames.CODE, "invalid-code");
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken authentication =
|
||||||
|
new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), parameters);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenPkceAndRequireProofKeyAndMissingCodeChallengeThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||||
|
.clientSettings(clientSettings -> clientSettings.requireProofKey(true))
|
||||||
|
.build();
|
||||||
|
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||||
|
.authorization(registeredClient)
|
||||||
|
.build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
Map<String, Object> parameters = createPkceTokenParameters(PLAIN_CODE_VERIFIER);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken authentication =
|
||||||
|
new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), parameters);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenPkceAndMissingCodeVerifierThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||||
|
.authorization(registeredClient, createPkceAuthorizationParametersPlain())
|
||||||
|
.build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
Map<String, Object> parameters = createPkceTokenParameters(PLAIN_CODE_VERIFIER);
|
||||||
|
parameters.remove(PkceParameterNames.CODE_VERIFIER);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken authentication =
|
||||||
|
new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), parameters);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenPkceAndPlainMethodAndInvalidCodeVerifierThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||||
|
.authorization(registeredClient, createPkceAuthorizationParametersPlain())
|
||||||
|
.build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
Map<String, Object> parameters = createPkceTokenParameters("invalid-code-verifier");
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken authentication =
|
||||||
|
new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), parameters);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenPkceAndS256MethodAndInvalidCodeVerifierThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||||
|
.authorization(registeredClient, createPkceAuthorizationParametersS256())
|
||||||
|
.build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
Map<String, Object> parameters = createPkceTokenParameters("invalid-code-verifier");
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken authentication =
|
||||||
|
new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), parameters);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenPkceAndPlainMethodAndValidCodeVerifierThenAuthenticated() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||||
|
.authorization(registeredClient, createPkceAuthorizationParametersPlain())
|
||||||
|
.build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
Map<String, Object> parameters = createPkceTokenParameters(PLAIN_CODE_VERIFIER);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken authentication =
|
||||||
|
new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), parameters);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken authenticationResult =
|
||||||
|
(OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
|
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||||
|
assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId());
|
||||||
|
assertThat(authenticationResult.getCredentials()).isNull();
|
||||||
|
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenPkceAndMissingMethodThenDefaultPlainMethodAndAuthenticated() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
Map<String, Object> authorizationRequestAdditionalParameters = createPkceAuthorizationParametersPlain();
|
||||||
|
authorizationRequestAdditionalParameters.remove(PkceParameterNames.CODE_CHALLENGE_METHOD);
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||||
|
.authorization(registeredClient, authorizationRequestAdditionalParameters)
|
||||||
|
.build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
Map<String, Object> parameters = createPkceTokenParameters(PLAIN_CODE_VERIFIER);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken authentication =
|
||||||
|
new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), parameters);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken authenticationResult =
|
||||||
|
(OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
|
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||||
|
assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId());
|
||||||
|
assertThat(authenticationResult.getCredentials()).isNull();
|
||||||
|
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenPkceAndS256MethodAndValidCodeVerifierThenAuthenticated() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||||
|
.authorization(registeredClient, createPkceAuthorizationParametersS256())
|
||||||
|
.build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
Map<String, Object> parameters = createPkceTokenParameters(S256_CODE_VERIFIER);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken authentication =
|
||||||
|
new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), parameters);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken authenticationResult =
|
||||||
|
(OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
|
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||||
|
assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId());
|
||||||
|
assertThat(authenticationResult.getCredentials()).isNull();
|
||||||
|
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenPkceAndUnsupportedCodeChallengeMethodThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
Map<String, Object> authorizationRequestAdditionalParameters = createPkceAuthorizationParametersPlain();
|
||||||
|
// This should never happen: the Authorization endpoint should not allow it
|
||||||
|
authorizationRequestAdditionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "unsupported-challenge-method");
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||||
|
.authorization(registeredClient, authorizationRequestAdditionalParameters)
|
||||||
|
.build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
Map<String, Object> parameters = createPkceTokenParameters(PLAIN_CODE_VERIFIER);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken authentication =
|
||||||
|
new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), parameters);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> createPkceTokenParameters(String codeVerifier) {
|
||||||
|
Map<String, Object> parameters = new HashMap<>();
|
||||||
|
parameters.put(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
|
||||||
|
parameters.put(OAuth2ParameterNames.CODE, AUTHORIZATION_CODE);
|
||||||
|
parameters.put(PkceParameterNames.CODE_VERIFIER, codeVerifier);
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> createPkceAuthorizationParametersPlain() {
|
||||||
|
Map<String, Object> parameters = new HashMap<>();
|
||||||
|
parameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "plain");
|
||||||
|
parameters.put(PkceParameterNames.CODE_CHALLENGE, PLAIN_CODE_CHALLENGE);
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> createPkceAuthorizationParametersS256() {
|
||||||
|
Map<String, Object> parameters = new HashMap<>();
|
||||||
|
parameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
|
||||||
|
parameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE);
|
||||||
|
return parameters;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import org.junit.Test;
|
|||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@@ -31,14 +34,14 @@ public class OAuth2ClientAuthenticationTokenTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenClientIdNullThenThrowIllegalArgumentException() {
|
public void constructorWhenClientIdNullThenThrowIllegalArgumentException() {
|
||||||
assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken(null, "secret"))
|
assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken(null, "secret", null))
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
.hasMessage("clientId cannot be empty");
|
.hasMessage("clientId cannot be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenClientSecretNullThenThrowIllegalArgumentException() {
|
public void constructorWhenClientSecretNullThenThrowIllegalArgumentException() {
|
||||||
assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken("clientId", null))
|
assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken("clientId", null, null))
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
.hasMessage("clientSecret cannot be empty");
|
.hasMessage("clientSecret cannot be empty");
|
||||||
}
|
}
|
||||||
@@ -52,13 +55,25 @@ public class OAuth2ClientAuthenticationTokenTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenClientCredentialsProvidedThenCreated() {
|
public void constructorWhenClientCredentialsProvidedThenCreated() {
|
||||||
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken("clientId", "secret");
|
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken("clientId", "secret", null);
|
||||||
assertThat(authentication.isAuthenticated()).isFalse();
|
assertThat(authentication.isAuthenticated()).isFalse();
|
||||||
assertThat(authentication.getPrincipal().toString()).isEqualTo("clientId");
|
assertThat(authentication.getPrincipal().toString()).isEqualTo("clientId");
|
||||||
assertThat(authentication.getCredentials()).isEqualTo("secret");
|
assertThat(authentication.getCredentials()).isEqualTo("secret");
|
||||||
assertThat(authentication.getRegisteredClient()).isNull();
|
assertThat(authentication.getRegisteredClient()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenClientIdProvidedThenCreated() {
|
||||||
|
Map<String, Object> additionalParameters = new HashMap<>();
|
||||||
|
additionalParameters.put("param1", "value1");
|
||||||
|
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken("clientId", additionalParameters);
|
||||||
|
assertThat(authentication.isAuthenticated()).isFalse();
|
||||||
|
assertThat(authentication.getPrincipal().toString()).isEqualTo("clientId");
|
||||||
|
assertThat(authentication.getCredentials()).isNull();
|
||||||
|
assertThat(authentication.getAdditionalParameters()).isEqualTo(additionalParameters);
|
||||||
|
assertThat(authentication.getRegisteredClient()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenRegisteredClientProvidedThenCreated() {
|
public void constructorWhenRegisteredClientProvidedThenCreated() {
|
||||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.jose.JoseHeaderNames;
|
import org.springframework.security.oauth2.jose.JoseHeaderNames;
|
||||||
@@ -49,14 +50,12 @@ import static org.mockito.Mockito.when;
|
|||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
*/
|
*/
|
||||||
public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
||||||
private RegisteredClient registeredClient;
|
|
||||||
private OAuth2AuthorizationService authorizationService;
|
private OAuth2AuthorizationService authorizationService;
|
||||||
private JwtEncoder jwtEncoder;
|
private JwtEncoder jwtEncoder;
|
||||||
private OAuth2ClientCredentialsAuthenticationProvider authenticationProvider;
|
private OAuth2ClientCredentialsAuthenticationProvider authenticationProvider;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
this.registeredClient = TestRegisteredClients.registeredClient().build();
|
|
||||||
this.authorizationService = mock(OAuth2AuthorizationService.class);
|
this.authorizationService = mock(OAuth2AuthorizationService.class);
|
||||||
this.jwtEncoder = mock(JwtEncoder.class);
|
this.jwtEncoder = mock(JwtEncoder.class);
|
||||||
this.authenticationProvider = new OAuth2ClientCredentialsAuthenticationProvider(
|
this.authenticationProvider = new OAuth2ClientCredentialsAuthenticationProvider(
|
||||||
@@ -89,8 +88,9 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() {
|
public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
|
||||||
TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(
|
TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(
|
||||||
this.registeredClient.getClientId(), this.registeredClient.getClientSecret());
|
registeredClient.getClientId(), registeredClient.getClientSecret());
|
||||||
OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal);
|
OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal);
|
||||||
|
|
||||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
@@ -102,8 +102,9 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
|
public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||||
this.registeredClient.getClientId(), this.registeredClient.getClientSecret());
|
registeredClient.getClientId(), registeredClient.getClientSecret(), null);
|
||||||
OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal);
|
OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal);
|
||||||
|
|
||||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
@@ -113,9 +114,25 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
|||||||
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenClientNotAuthorizedToRequestTokenThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2()
|
||||||
|
.authorizationGrantTypes(grantTypes -> grantTypes.remove(AuthorizationGrantType.CLIENT_CREDENTIALS))
|
||||||
|
.build();
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenInvalidScopeThenThrowOAuth2AuthenticationException() {
|
public void authenticateWhenInvalidScopeThenThrowOAuth2AuthenticationException() {
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient);
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(
|
OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(
|
||||||
clientPrincipal, Collections.singleton("invalid-scope"));
|
clientPrincipal, Collections.singleton("invalid-scope"));
|
||||||
|
|
||||||
@@ -128,7 +145,8 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenScopeRequestedThenAccessTokenContainsScope() {
|
public void authenticateWhenScopeRequestedThenAccessTokenContainsScope() {
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient);
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
Set<String> requestedScope = Collections.singleton("openid");
|
Set<String> requestedScope = Collections.singleton("openid");
|
||||||
OAuth2ClientCredentialsAuthenticationToken authentication =
|
OAuth2ClientCredentialsAuthenticationToken authentication =
|
||||||
new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, requestedScope);
|
new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, requestedScope);
|
||||||
@@ -142,7 +160,8 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenValidAuthenticationThenReturnAccessToken() {
|
public void authenticateWhenValidAuthenticationThenReturnAccessToken() {
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient);
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal);
|
OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal);
|
||||||
|
|
||||||
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
|
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
|
||||||
@@ -156,10 +175,10 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
|||||||
|
|
||||||
assertThat(authorization.getRegisteredClientId()).isEqualTo(clientPrincipal.getRegisteredClient().getId());
|
assertThat(authorization.getRegisteredClientId()).isEqualTo(clientPrincipal.getRegisteredClient().getId());
|
||||||
assertThat(authorization.getPrincipalName()).isEqualTo(clientPrincipal.getName());
|
assertThat(authorization.getPrincipalName()).isEqualTo(clientPrincipal.getName());
|
||||||
assertThat(authorization.getAccessToken()).isNotNull();
|
assertThat(authorization.getTokens().getAccessToken()).isNotNull();
|
||||||
assertThat(authorization.getAccessToken().getScopes()).isEqualTo(clientPrincipal.getRegisteredClient().getScopes());
|
assertThat(authorization.getTokens().getAccessToken().getScopes()).isEqualTo(clientPrincipal.getRegisteredClient().getScopes());
|
||||||
assertThat(accessTokenAuthentication.getPrincipal()).isEqualTo(clientPrincipal);
|
assertThat(accessTokenAuthentication.getPrincipal()).isEqualTo(clientPrincipal);
|
||||||
assertThat(accessTokenAuthentication.getAccessToken()).isEqualTo(authorization.getAccessToken());
|
assertThat(accessTokenAuthentication.getAccessToken()).isEqualTo(authorization.getTokens().getAccessToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Jwt createJwt() {
|
private static Jwt createJwt() {
|
||||||
|
|||||||
@@ -0,0 +1,314 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
|
||||||
|
import org.springframework.security.oauth2.jose.JoseHeaderNames;
|
||||||
|
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
|
||||||
|
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link OAuth2RefreshTokenAuthenticationProvider}.
|
||||||
|
*
|
||||||
|
* @author Alexey Nesterov
|
||||||
|
* @since 0.0.3
|
||||||
|
*/
|
||||||
|
public class OAuth2RefreshTokenAuthenticationProviderTests {
|
||||||
|
private OAuth2AuthorizationService authorizationService;
|
||||||
|
private JwtEncoder jwtEncoder;
|
||||||
|
private OAuth2RefreshTokenAuthenticationProvider authenticationProvider;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
this.authorizationService = mock(OAuth2AuthorizationService.class);
|
||||||
|
this.jwtEncoder = mock(JwtEncoder.class);
|
||||||
|
Jwt jwt = Jwt.withTokenValue("refreshed-access-token")
|
||||||
|
.header(JoseHeaderNames.ALG, SignatureAlgorithm.RS256.getName())
|
||||||
|
.issuedAt(Instant.now())
|
||||||
|
.expiresAt(Instant.now().plus(1, ChronoUnit.HOURS))
|
||||||
|
.build();
|
||||||
|
when(this.jwtEncoder.encode(any(), any())).thenReturn(jwt);
|
||||||
|
this.authenticationProvider = new OAuth2RefreshTokenAuthenticationProvider(
|
||||||
|
this.authorizationService, this.jwtEncoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationProvider(null, this.jwtEncoder))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.extracting(Throwable::getMessage)
|
||||||
|
.isEqualTo("authorizationService cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenJwtEncoderNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationProvider(this.authorizationService, null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.extracting(Throwable::getMessage)
|
||||||
|
.isEqualTo("jwtEncoder cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportsWhenSupportedAuthenticationThenTrue() {
|
||||||
|
assertThat(this.authenticationProvider.supports(OAuth2RefreshTokenAuthenticationToken.class)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportsWhenUnsupportedAuthenticationThenFalse() {
|
||||||
|
assertThat(this.authenticationProvider.supports(OAuth2ClientCredentialsAuthenticationToken.class)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenValidRefreshTokenThenReturnAccessToken() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
when(this.authorizationService.findByToken(
|
||||||
|
eq(authorization.getTokens().getRefreshToken().getTokenValue()),
|
||||||
|
eq(TokenType.REFRESH_TOKEN)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
|
||||||
|
authorization.getTokens().getRefreshToken().getTokenValue(), clientPrincipal);
|
||||||
|
|
||||||
|
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
||||||
|
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
|
|
||||||
|
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||||
|
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||||
|
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||||
|
|
||||||
|
assertThat(accessTokenAuthentication.getRegisteredClient().getId()).isEqualTo(updatedAuthorization.getRegisteredClientId());
|
||||||
|
assertThat(accessTokenAuthentication.getPrincipal()).isEqualTo(clientPrincipal);
|
||||||
|
assertThat(accessTokenAuthentication.getAccessToken()).isEqualTo(updatedAuthorization.getTokens().getAccessToken());
|
||||||
|
assertThat(updatedAuthorization.getTokens().getAccessToken()).isNotEqualTo(authorization.getTokens().getAccessToken());
|
||||||
|
assertThat(accessTokenAuthentication.getRefreshToken()).isEqualTo(updatedAuthorization.getTokens().getRefreshToken());
|
||||||
|
// By default, refresh token is reused
|
||||||
|
assertThat(updatedAuthorization.getTokens().getRefreshToken()).isEqualTo(authorization.getTokens().getRefreshToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenReuseRefreshTokensFalseThenReturnNewRefreshToken() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||||
|
.tokenSettings(tokenSettings -> tokenSettings.reuseRefreshTokens(false))
|
||||||
|
.build();
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
when(this.authorizationService.findByToken(
|
||||||
|
eq(authorization.getTokens().getRefreshToken().getTokenValue()),
|
||||||
|
eq(TokenType.REFRESH_TOKEN)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
|
||||||
|
authorization.getTokens().getRefreshToken().getTokenValue(), clientPrincipal);
|
||||||
|
|
||||||
|
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
||||||
|
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
|
|
||||||
|
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||||
|
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||||
|
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||||
|
|
||||||
|
assertThat(accessTokenAuthentication.getRefreshToken()).isEqualTo(updatedAuthorization.getTokens().getRefreshToken());
|
||||||
|
assertThat(updatedAuthorization.getTokens().getRefreshToken()).isNotEqualTo(authorization.getTokens().getRefreshToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenRequestedScopesAuthorizedThenAccessTokenIncludesScopes() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
when(this.authorizationService.findByToken(
|
||||||
|
eq(authorization.getTokens().getRefreshToken().getTokenValue()),
|
||||||
|
eq(TokenType.REFRESH_TOKEN)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
Set<String> authorizedScopes = authorization.getAttribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES);
|
||||||
|
Set<String> requestedScopes = new HashSet<>(authorizedScopes);
|
||||||
|
requestedScopes.remove("email");
|
||||||
|
OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
|
||||||
|
authorization.getTokens().getRefreshToken().getTokenValue(), clientPrincipal, requestedScopes);
|
||||||
|
|
||||||
|
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
||||||
|
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
|
|
||||||
|
assertThat(accessTokenAuthentication.getAccessToken().getScopes()).isEqualTo(requestedScopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenRequestedScopesNotAuthorizedThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
when(this.authorizationService.findByToken(
|
||||||
|
eq(authorization.getTokens().getRefreshToken().getTokenValue()),
|
||||||
|
eq(TokenType.REFRESH_TOKEN)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
Set<String> authorizedScopes = authorization.getAttribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES);
|
||||||
|
Set<String> requestedScopes = new HashSet<>(authorizedScopes);
|
||||||
|
requestedScopes.add("unauthorized");
|
||||||
|
OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
|
||||||
|
authorization.getTokens().getRefreshToken().getTokenValue(), clientPrincipal, requestedScopes);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_SCOPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenInvalidRefreshTokenThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
|
||||||
|
"invalid", clientPrincipal);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(
|
||||||
|
registeredClient.getClientId(), registeredClient.getClientSecret());
|
||||||
|
OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
|
||||||
|
"refresh-token", clientPrincipal);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||||
|
registeredClient.getClientId(), registeredClient.getClientSecret(), null);
|
||||||
|
OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
|
||||||
|
"refresh-token", clientPrincipal);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenRefreshTokenIssuedToAnotherClientThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
when(this.authorizationService.findByToken(
|
||||||
|
eq(authorization.getTokens().getRefreshToken().getTokenValue()),
|
||||||
|
eq(TokenType.REFRESH_TOKEN)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||||
|
TestRegisteredClients.registeredClient2().build());
|
||||||
|
OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
|
||||||
|
authorization.getTokens().getRefreshToken().getTokenValue(), clientPrincipal);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenClientNotAuthorizedToRefreshTokenThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||||
|
.authorizationGrantTypes(grantTypes -> grantTypes.remove(AuthorizationGrantType.REFRESH_TOKEN))
|
||||||
|
.build();
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
when(this.authorizationService.findByToken(
|
||||||
|
eq(authorization.getTokens().getRefreshToken().getTokenValue()),
|
||||||
|
eq(TokenType.REFRESH_TOKEN)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
|
||||||
|
authorization.getTokens().getRefreshToken().getTokenValue(), clientPrincipal);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenExpiredRefreshTokenThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
OAuth2RefreshToken expiredRefreshToken = new OAuth2RefreshToken2(
|
||||||
|
"expired-refresh-token", Instant.now().minusSeconds(120), Instant.now().minusSeconds(60));
|
||||||
|
OAuth2Tokens tokens = OAuth2Tokens.from(authorization.getTokens()).refreshToken(expiredRefreshToken).build();
|
||||||
|
authorization = OAuth2Authorization.from(authorization).tokens(tokens).build();
|
||||||
|
when(this.authorizationService.findByToken(
|
||||||
|
eq(authorization.getTokens().getRefreshToken().getTokenValue()),
|
||||||
|
eq(TokenType.REFRESH_TOKEN)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
|
||||||
|
authorization.getTokens().getRefreshToken().getTokenValue(), clientPrincipal);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link OAuth2RefreshTokenAuthenticationToken}.
|
||||||
|
*
|
||||||
|
* @author Alexey Nesterov
|
||||||
|
* @since 0.0.3
|
||||||
|
*/
|
||||||
|
public class OAuth2RefreshTokenAuthenticationTokenTests {
|
||||||
|
private final OAuth2ClientAuthenticationToken clientPrincipal =
|
||||||
|
new OAuth2ClientAuthenticationToken(TestRegisteredClients.registeredClient().build());
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenRefreshTokenNullOrEmptyThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken(null, this.clientPrincipal))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("refreshToken cannot be empty");
|
||||||
|
assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken("", this.clientPrincipal))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("refreshToken cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken("refresh-token", null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("clientPrincipal cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenScopesNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken("refresh-token", this.clientPrincipal, null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("scopes cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenScopesProvidedThenCreated() {
|
||||||
|
Set<String> expectedScopes = new HashSet<>(Arrays.asList("scope-a", "scope-b"));
|
||||||
|
OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
|
||||||
|
"refresh-token", this.clientPrincipal, expectedScopes);
|
||||||
|
assertThat(authentication.getRefreshToken()).isEqualTo("refresh-token");
|
||||||
|
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
|
||||||
|
assertThat(authentication.getCredentials().toString()).isEmpty();
|
||||||
|
assertThat(authentication.getScopes()).isEqualTo(expectedScopes);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes2;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link OAuth2TokenRevocationAuthenticationProvider}.
|
||||||
|
*
|
||||||
|
* @author Vivek Babu
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
public class OAuth2TokenRevocationAuthenticationProviderTests {
|
||||||
|
private OAuth2AuthorizationService authorizationService;
|
||||||
|
private OAuth2TokenRevocationAuthenticationProvider authenticationProvider;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
this.authorizationService = mock(OAuth2AuthorizationService.class);
|
||||||
|
this.authenticationProvider = new OAuth2TokenRevocationAuthenticationProvider(this.authorizationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2TokenRevocationAuthenticationProvider(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("authorizationService cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportsWhenTypeOAuth2TokenRevocationAuthenticationTokenThenReturnTrue() {
|
||||||
|
assertThat(this.authenticationProvider.supports(OAuth2TokenRevocationAuthenticationToken.class)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(
|
||||||
|
registeredClient.getClientId(), registeredClient.getClientSecret());
|
||||||
|
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||||
|
"token", clientPrincipal, TokenType.ACCESS_TOKEN.getValue());
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||||
|
registeredClient.getClientId(), registeredClient.getClientSecret(), null);
|
||||||
|
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||||
|
"token", clientPrincipal, TokenType.ACCESS_TOKEN.getValue());
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenInvalidTokenTypeThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||||
|
"token", clientPrincipal, OAuth2ErrorCodes2.UNSUPPORTED_TOKEN_TYPE);
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes2.UNSUPPORTED_TOKEN_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenInvalidTokenThenNotRevoked() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||||
|
"token", clientPrincipal, TokenType.ACCESS_TOKEN.getValue());
|
||||||
|
OAuth2TokenRevocationAuthenticationToken authenticationResult =
|
||||||
|
(OAuth2TokenRevocationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
|
assertThat(authenticationResult.isAuthenticated()).isFalse();
|
||||||
|
verify(this.authorizationService, never()).save(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenTokenIssuedToAnotherClientThenThrowOAuth2AuthenticationException() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
|
||||||
|
TestRegisteredClients.registeredClient2().build()).build();
|
||||||
|
when(this.authorizationService.findByToken(
|
||||||
|
eq("token"),
|
||||||
|
eq(TokenType.ACCESS_TOKEN)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||||
|
"token", clientPrincipal, TokenType.ACCESS_TOKEN.getValue());
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||||
|
.extracting("errorCode")
|
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenValidRefreshTokenThenRevoked() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
|
||||||
|
registeredClient).build();
|
||||||
|
when(this.authorizationService.findByToken(
|
||||||
|
eq(authorization.getTokens().getRefreshToken().getTokenValue()),
|
||||||
|
eq(TokenType.REFRESH_TOKEN)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||||
|
authorization.getTokens().getRefreshToken().getTokenValue(), clientPrincipal, TokenType.REFRESH_TOKEN.getValue());
|
||||||
|
|
||||||
|
OAuth2TokenRevocationAuthenticationToken authenticationResult =
|
||||||
|
(OAuth2TokenRevocationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
|
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||||
|
|
||||||
|
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||||
|
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||||
|
|
||||||
|
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||||
|
OAuth2RefreshToken refreshToken = updatedAuthorization.getTokens().getRefreshToken();
|
||||||
|
assertThat(updatedAuthorization.getTokens().getTokenMetadata(refreshToken).isInvalidated()).isTrue();
|
||||||
|
OAuth2AccessToken accessToken = updatedAuthorization.getTokens().getAccessToken();
|
||||||
|
assertThat(updatedAuthorization.getTokens().getTokenMetadata(accessToken).isInvalidated()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenValidAccessTokenThenRevoked() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
|
||||||
|
registeredClient).build();
|
||||||
|
when(this.authorizationService.findByToken(
|
||||||
|
eq(authorization.getTokens().getAccessToken().getTokenValue()),
|
||||||
|
eq(TokenType.ACCESS_TOKEN)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||||
|
authorization.getTokens().getAccessToken().getTokenValue(), clientPrincipal, TokenType.ACCESS_TOKEN.getValue());
|
||||||
|
|
||||||
|
OAuth2TokenRevocationAuthenticationToken authenticationResult =
|
||||||
|
(OAuth2TokenRevocationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
|
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||||
|
|
||||||
|
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||||
|
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||||
|
|
||||||
|
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||||
|
OAuth2AccessToken accessToken = updatedAuthorization.getTokens().getAccessToken();
|
||||||
|
assertThat(updatedAuthorization.getTokens().getTokenMetadata(accessToken).isInvalidated()).isTrue();
|
||||||
|
OAuth2RefreshToken refreshToken = updatedAuthorization.getTokens().getRefreshToken();
|
||||||
|
assertThat(updatedAuthorization.getTokens().getTokenMetadata(refreshToken).isInvalidated()).isFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link OAuth2TokenRevocationAuthenticationToken}.
|
||||||
|
*
|
||||||
|
* @author Vivek Babu
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
public class OAuth2TokenRevocationAuthenticationTokenTests {
|
||||||
|
private String token = "token";
|
||||||
|
private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||||
|
TestRegisteredClients.registeredClient().build());
|
||||||
|
private String tokenTypeHint = TokenType.ACCESS_TOKEN.getValue();
|
||||||
|
private OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||||
|
OAuth2AccessToken.TokenType.BEARER, this.token,
|
||||||
|
Instant.now(), Instant.now().plus(Duration.ofHours(1)));
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenTokenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2TokenRevocationAuthenticationToken(null, this.clientPrincipal, this.tokenTypeHint))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("token cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2TokenRevocationAuthenticationToken(this.token, null, this.tokenTypeHint))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("clientPrincipal cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenRevokedTokenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2TokenRevocationAuthenticationToken(null, this.clientPrincipal))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("revokedToken cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenRevokedTokenAndClientPrincipalNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2TokenRevocationAuthenticationToken(this.accessToken, null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("clientPrincipal cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenTokenProvidedThenCreated() {
|
||||||
|
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||||
|
this.token, this.clientPrincipal, this.tokenTypeHint);
|
||||||
|
assertThat(authentication.getToken()).isEqualTo(this.token);
|
||||||
|
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
|
||||||
|
assertThat(authentication.getTokenTypeHint()).isEqualTo(this.tokenTypeHint);
|
||||||
|
assertThat(authentication.getCredentials().toString()).isEmpty();
|
||||||
|
assertThat(authentication.isAuthenticated()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenRevokedTokenProvidedThenCreated() {
|
||||||
|
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||||
|
this.accessToken, this.clientPrincipal);
|
||||||
|
assertThat(authentication.getToken()).isEqualTo(this.accessToken.getTokenValue());
|
||||||
|
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
|
||||||
|
assertThat(authentication.getTokenTypeHint()).isNull();
|
||||||
|
assertThat(authentication.getCredentials().toString()).isEmpty();
|
||||||
|
assertThat(authentication.isAuthenticated()).isTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -322,6 +322,9 @@ public class RegisteredClientTests {
|
|||||||
RegisteredClient registration = TestRegisteredClients.registeredClient().build();
|
RegisteredClient registration = TestRegisteredClients.registeredClient().build();
|
||||||
RegisteredClient updated = RegisteredClient.withRegisteredClient(registration).build();
|
RegisteredClient updated = RegisteredClient.withRegisteredClient(registration).build();
|
||||||
|
|
||||||
|
assertThat(registration.getId()).isEqualTo(updated.getId());
|
||||||
|
assertThat(registration.getClientId()).isEqualTo(updated.getClientId());
|
||||||
|
assertThat(registration.getClientSecret()).isEqualTo(updated.getClientSecret());
|
||||||
assertThat(registration.getClientAuthenticationMethods()).isEqualTo(updated.getClientAuthenticationMethods());
|
assertThat(registration.getClientAuthenticationMethods()).isEqualTo(updated.getClientAuthenticationMethods());
|
||||||
assertThat(registration.getClientAuthenticationMethods()).isNotSameAs(updated.getClientAuthenticationMethods());
|
assertThat(registration.getClientAuthenticationMethods()).isNotSameAs(updated.getClientAuthenticationMethods());
|
||||||
assertThat(registration.getAuthorizationGrantTypes()).isEqualTo(updated.getAuthorizationGrantTypes());
|
assertThat(registration.getAuthorizationGrantTypes()).isEqualTo(updated.getAuthorizationGrantTypes());
|
||||||
@@ -330,20 +333,10 @@ public class RegisteredClientTests {
|
|||||||
assertThat(registration.getRedirectUris()).isNotSameAs(updated.getRedirectUris());
|
assertThat(registration.getRedirectUris()).isNotSameAs(updated.getRedirectUris());
|
||||||
assertThat(registration.getScopes()).isEqualTo(updated.getScopes());
|
assertThat(registration.getScopes()).isEqualTo(updated.getScopes());
|
||||||
assertThat(registration.getScopes()).isNotSameAs(updated.getScopes());
|
assertThat(registration.getScopes()).isNotSameAs(updated.getScopes());
|
||||||
}
|
assertThat(registration.getClientSettings().settings()).isEqualTo(updated.getClientSettings().settings());
|
||||||
|
assertThat(registration.getClientSettings()).isNotSameAs(updated.getClientSettings());
|
||||||
@Test
|
assertThat(registration.getTokenSettings().settings()).isEqualTo(updated.getTokenSettings().settings());
|
||||||
public void buildWhenRegisteredClientProvidedThenEachPropertyMatches() {
|
assertThat(registration.getTokenSettings()).isNotSameAs(updated.getTokenSettings());
|
||||||
RegisteredClient registration = TestRegisteredClients.registeredClient().build();
|
|
||||||
RegisteredClient updated = RegisteredClient.withRegisteredClient(registration).build();
|
|
||||||
|
|
||||||
assertThat(registration.getId()).isEqualTo(updated.getId());
|
|
||||||
assertThat(registration.getClientId()).isEqualTo(updated.getClientId());
|
|
||||||
assertThat(registration.getClientSecret()).isEqualTo(updated.getClientSecret());
|
|
||||||
assertThat(registration.getClientAuthenticationMethods()).isEqualTo(updated.getClientAuthenticationMethods());
|
|
||||||
assertThat(registration.getAuthorizationGrantTypes()).isEqualTo(updated.getAuthorizationGrantTypes());
|
|
||||||
assertThat(registration.getRedirectUris()).isEqualTo(updated.getRedirectUris());
|
|
||||||
assertThat(registration.getScopes()).isEqualTo(updated.getScopes());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public class TestRegisteredClients {
|
|||||||
.clientId("client-1")
|
.clientId("client-1")
|
||||||
.clientSecret("secret")
|
.clientSecret("secret")
|
||||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
||||||
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
||||||
.redirectUri("https://example.com")
|
.redirectUri("https://example.com")
|
||||||
.scope("openid")
|
.scope("openid")
|
||||||
@@ -40,6 +41,7 @@ public class TestRegisteredClients {
|
|||||||
.clientId("client-2")
|
.clientId("client-2")
|
||||||
.clientSecret("secret")
|
.clientSecret("secret")
|
||||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
||||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||||
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
||||||
.redirectUri("https://example.com")
|
.redirectUri("https://example.com")
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.config;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ClientSettings}.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
public class ClientSettingsTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenDefaultThenDefaultsAreSet() {
|
||||||
|
ClientSettings clientSettings = new ClientSettings();
|
||||||
|
assertThat(clientSettings.settings()).hasSize(2);
|
||||||
|
assertThat(clientSettings.requireProofKey()).isFalse();
|
||||||
|
assertThat(clientSettings.requireUserConsent()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new ClientSettings(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("settings cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void requireProofKeyWhenTrueThenSet() {
|
||||||
|
ClientSettings clientSettings = new ClientSettings().requireProofKey(true);
|
||||||
|
assertThat(clientSettings.requireProofKey()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void requireUserConsentWhenTrueThenSet() {
|
||||||
|
ClientSettings clientSettings = new ClientSettings().requireUserConsent(true);
|
||||||
|
assertThat(clientSettings.requireUserConsent()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void settingWhenCalledThenReturnClientSettings() {
|
||||||
|
ClientSettings clientSettings = new ClientSettings()
|
||||||
|
.<ClientSettings>setting("name1", "value1")
|
||||||
|
.requireProofKey(true)
|
||||||
|
.<ClientSettings>settings(settings -> settings.put("name2", "value2"))
|
||||||
|
.requireUserConsent(true);
|
||||||
|
assertThat(clientSettings.settings()).hasSize(4);
|
||||||
|
assertThat(clientSettings.requireProofKey()).isTrue();
|
||||||
|
assertThat(clientSettings.requireUserConsent()).isTrue();
|
||||||
|
assertThat(clientSettings.<String>setting("name1")).isEqualTo("value1");
|
||||||
|
assertThat(clientSettings.<String>setting("name2")).isEqualTo("value2");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.config;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.assertj.core.api.Assertions.entry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link Settings}.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
public class SettingsTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new Settings(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("settings cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenSettingsProvidedThenSettingsAreSet() {
|
||||||
|
Map<String, Object> initialSettings = new HashMap<>();
|
||||||
|
initialSettings.put("setting1", "value1");
|
||||||
|
initialSettings.put("setting2", "value2");
|
||||||
|
|
||||||
|
Settings settings = new Settings(initialSettings)
|
||||||
|
.setting("setting3", "value3")
|
||||||
|
.settings(s -> s.put("setting4", "value4"));
|
||||||
|
|
||||||
|
assertThat(settings.settings()).contains(
|
||||||
|
entry("setting1", "value1"),
|
||||||
|
entry("setting2", "value2"),
|
||||||
|
entry("setting3", "value3"),
|
||||||
|
entry("setting4", "value4"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getSettingWhenNameNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new Settings().setting(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("name cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setSettingWhenNameNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new Settings().setting(null, "value"))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("name cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setSettingWhenValueNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new Settings().setting("setting", null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("value cannot be null");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.config;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link TokenSettings}.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
public class TokenSettingsTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenDefaultThenDefaultsAreSet() {
|
||||||
|
TokenSettings tokenSettings = new TokenSettings();
|
||||||
|
assertThat(tokenSettings.settings()).hasSize(4);
|
||||||
|
assertThat(tokenSettings.accessTokenTimeToLive()).isEqualTo(Duration.ofMinutes(5));
|
||||||
|
assertThat(tokenSettings.enableRefreshTokens()).isTrue();
|
||||||
|
assertThat(tokenSettings.reuseRefreshTokens()).isTrue();
|
||||||
|
assertThat(tokenSettings.refreshTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new TokenSettings(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("settings cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void accessTokenTimeToLiveWhenProvidedThenSet() {
|
||||||
|
Duration accessTokenTimeToLive = Duration.ofMinutes(10);
|
||||||
|
TokenSettings tokenSettings = new TokenSettings().accessTokenTimeToLive(accessTokenTimeToLive);
|
||||||
|
assertThat(tokenSettings.accessTokenTimeToLive()).isEqualTo(accessTokenTimeToLive);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void accessTokenTimeToLiveWhenNullOrZeroOrNegativeThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new TokenSettings().accessTokenTimeToLive(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.extracting(Throwable::getMessage)
|
||||||
|
.isEqualTo("accessTokenTimeToLive cannot be null");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> new TokenSettings().accessTokenTimeToLive(Duration.ZERO))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.extracting(Throwable::getMessage)
|
||||||
|
.isEqualTo("accessTokenTimeToLive must be greater than Duration.ZERO");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> new TokenSettings().accessTokenTimeToLive(Duration.ofSeconds(-10)))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.extracting(Throwable::getMessage)
|
||||||
|
.isEqualTo("accessTokenTimeToLive must be greater than Duration.ZERO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void enableRefreshTokensWhenFalseThenSet() {
|
||||||
|
TokenSettings tokenSettings = new TokenSettings().enableRefreshTokens(false);
|
||||||
|
assertThat(tokenSettings.enableRefreshTokens()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reuseRefreshTokensWhenFalseThenSet() {
|
||||||
|
TokenSettings tokenSettings = new TokenSettings().reuseRefreshTokens(false);
|
||||||
|
assertThat(tokenSettings.reuseRefreshTokens()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void refreshTokenTimeToLiveWhenProvidedThenSet() {
|
||||||
|
Duration refreshTokenTimeToLive = Duration.ofDays(10);
|
||||||
|
TokenSettings tokenSettings = new TokenSettings().refreshTokenTimeToLive(refreshTokenTimeToLive);
|
||||||
|
assertThat(tokenSettings.refreshTokenTimeToLive()).isEqualTo(refreshTokenTimeToLive);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void refreshTokenTimeToLiveWhenNullOrZeroOrNegativeThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new TokenSettings().refreshTokenTimeToLive(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.extracting(Throwable::getMessage)
|
||||||
|
.isEqualTo("refreshTokenTimeToLive cannot be null");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> new TokenSettings().refreshTokenTimeToLive(Duration.ZERO))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.extracting(Throwable::getMessage)
|
||||||
|
.isEqualTo("refreshTokenTimeToLive must be greater than Duration.ZERO");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> new TokenSettings().refreshTokenTimeToLive(Duration.ofSeconds(-10)))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.extracting(Throwable::getMessage)
|
||||||
|
.isEqualTo("refreshTokenTimeToLive must be greater than Duration.ZERO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void settingWhenCalledThenReturnTokenSettings() {
|
||||||
|
Duration accessTokenTimeToLive = Duration.ofMinutes(10);
|
||||||
|
TokenSettings tokenSettings = new TokenSettings()
|
||||||
|
.<TokenSettings>setting("name1", "value1")
|
||||||
|
.accessTokenTimeToLive(accessTokenTimeToLive)
|
||||||
|
.<TokenSettings>settings(settings -> settings.put("name2", "value2"));
|
||||||
|
assertThat(tokenSettings.settings()).hasSize(6);
|
||||||
|
assertThat(tokenSettings.accessTokenTimeToLive()).isEqualTo(accessTokenTimeToLive);
|
||||||
|
assertThat(tokenSettings.enableRefreshTokens()).isTrue();
|
||||||
|
assertThat(tokenSettings.reuseRefreshTokens()).isTrue();
|
||||||
|
assertThat(tokenSettings.refreshTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60));
|
||||||
|
assertThat(tokenSettings.<String>setting("name1")).isEqualTo("value1");
|
||||||
|
assertThat(tokenSettings.<String>setting("name2")).isEqualTo("value2");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.token;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link OAuth2TokenMetadata}.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
public class OAuth2TokenMetadataTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void metadataWhenNameNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() ->
|
||||||
|
OAuth2TokenMetadata.builder()
|
||||||
|
.metadata(null, "value"))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("name cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void metadataWhenValueNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() ->
|
||||||
|
OAuth2TokenMetadata.builder()
|
||||||
|
.metadata("name", null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("value cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getMetadataWhenNameNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> OAuth2TokenMetadata.builder().build().getMetadata(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("name cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void buildWhenDefaultThenDefaultsAreSet() {
|
||||||
|
OAuth2TokenMetadata tokenMetadata = OAuth2TokenMetadata.builder().build();
|
||||||
|
assertThat(tokenMetadata.getMetadata()).hasSize(1);
|
||||||
|
assertThat(tokenMetadata.isInvalidated()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void buildWhenMetadataProvidedThenMetadataIsSet() {
|
||||||
|
OAuth2TokenMetadata tokenMetadata = OAuth2TokenMetadata.builder()
|
||||||
|
.invalidated()
|
||||||
|
.metadata("name1", "value1")
|
||||||
|
.metadata(metadata -> metadata.put("name2", "value2"))
|
||||||
|
.build();
|
||||||
|
assertThat(tokenMetadata.getMetadata()).hasSize(3);
|
||||||
|
assertThat(tokenMetadata.isInvalidated()).isTrue();
|
||||||
|
assertThat(tokenMetadata.<String>getMetadata("name1")).isEqualTo("value1");
|
||||||
|
assertThat(tokenMetadata.<String>getMetadata("name2")).isEqualTo("value2");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.server.authorization.token;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link OAuth2Tokens}.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
public class OAuth2TokensTests {
|
||||||
|
private OAuth2AccessToken accessToken;
|
||||||
|
private OAuth2RefreshToken refreshToken;
|
||||||
|
private OidcIdToken idToken;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
Instant issuedAt = Instant.now();
|
||||||
|
this.accessToken = new OAuth2AccessToken(
|
||||||
|
OAuth2AccessToken.TokenType.BEARER,
|
||||||
|
"access-token",
|
||||||
|
issuedAt,
|
||||||
|
issuedAt.plus(Duration.ofMinutes(5)),
|
||||||
|
new HashSet<>(Arrays.asList("read", "write")));
|
||||||
|
this.refreshToken = new OAuth2RefreshToken(
|
||||||
|
"refresh-token",
|
||||||
|
issuedAt);
|
||||||
|
this.idToken = OidcIdToken.withTokenValue("id-token")
|
||||||
|
.issuer("https://provider.com")
|
||||||
|
.subject("subject")
|
||||||
|
.issuedAt(issuedAt)
|
||||||
|
.expiresAt(issuedAt.plus(Duration.ofMinutes(30)))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void accessTokenWhenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> OAuth2Tokens.builder().accessToken(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("token cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void refreshTokenWhenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> OAuth2Tokens.builder().refreshToken(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("token cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tokenWhenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> OAuth2Tokens.builder().token(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("token cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getTokenWhenTokenTypeNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> OAuth2Tokens.builder().build().getToken((Class<OAuth2AccessToken>) null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("tokenType cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getTokenWhenTokenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> OAuth2Tokens.builder().build().getToken((String) null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("token cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getTokenMetadataWhenTokenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> OAuth2Tokens.builder().build().getTokenMetadata(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("token cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fromWhenTokensNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> OAuth2Tokens.from(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("tokens cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fromWhenTokensProvidedThenCopied() {
|
||||||
|
OAuth2Tokens tokens = OAuth2Tokens.builder()
|
||||||
|
.accessToken(this.accessToken)
|
||||||
|
.refreshToken(this.refreshToken)
|
||||||
|
.token(this.idToken)
|
||||||
|
.build();
|
||||||
|
OAuth2Tokens tokensResult = OAuth2Tokens.from(tokens).build();
|
||||||
|
|
||||||
|
assertThat(tokensResult.getAccessToken()).isEqualTo(tokens.getAccessToken());
|
||||||
|
assertThat(tokensResult.getTokenMetadata(tokensResult.getAccessToken()))
|
||||||
|
.isEqualTo(tokens.getTokenMetadata(tokens.getAccessToken()));
|
||||||
|
|
||||||
|
assertThat(tokensResult.getRefreshToken()).isEqualTo(tokens.getRefreshToken());
|
||||||
|
assertThat(tokensResult.getTokenMetadata(tokensResult.getRefreshToken()))
|
||||||
|
.isEqualTo(tokens.getTokenMetadata(tokens.getRefreshToken()));
|
||||||
|
|
||||||
|
assertThat(tokensResult.getToken(OidcIdToken.class)).isEqualTo(tokens.getToken(OidcIdToken.class));
|
||||||
|
assertThat(tokensResult.getTokenMetadata(tokensResult.getToken(OidcIdToken.class)))
|
||||||
|
.isEqualTo(tokens.getTokenMetadata(tokens.getToken(OidcIdToken.class)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void buildWhenTokenMetadataNotProvidedThenDefaultsAreSet() {
|
||||||
|
OAuth2Tokens tokens = OAuth2Tokens.builder()
|
||||||
|
.accessToken(this.accessToken)
|
||||||
|
.refreshToken(this.refreshToken)
|
||||||
|
.token(this.idToken)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(tokens.getAccessToken()).isEqualTo(this.accessToken);
|
||||||
|
OAuth2TokenMetadata tokenMetadata = tokens.getTokenMetadata(tokens.getAccessToken());
|
||||||
|
assertThat(tokenMetadata.isInvalidated()).isFalse();
|
||||||
|
|
||||||
|
assertThat(tokens.getRefreshToken()).isEqualTo(this.refreshToken);
|
||||||
|
tokenMetadata = tokens.getTokenMetadata(tokens.getRefreshToken());
|
||||||
|
assertThat(tokenMetadata.isInvalidated()).isFalse();
|
||||||
|
|
||||||
|
assertThat(tokens.getToken(OidcIdToken.class)).isEqualTo(this.idToken);
|
||||||
|
tokenMetadata = tokens.getTokenMetadata(tokens.getToken(OidcIdToken.class));
|
||||||
|
assertThat(tokenMetadata.isInvalidated()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void buildWhenTokenMetadataProvidedThenTokenMetadataIsSet() {
|
||||||
|
OAuth2TokenMetadata expectedTokenMetadata = OAuth2TokenMetadata.builder().build();
|
||||||
|
OAuth2Tokens tokens = OAuth2Tokens.builder()
|
||||||
|
.accessToken(this.accessToken, expectedTokenMetadata)
|
||||||
|
.refreshToken(this.refreshToken, expectedTokenMetadata)
|
||||||
|
.token(this.idToken, expectedTokenMetadata)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(tokens.getAccessToken()).isEqualTo(this.accessToken);
|
||||||
|
OAuth2TokenMetadata tokenMetadata = tokens.getTokenMetadata(tokens.getAccessToken());
|
||||||
|
assertThat(tokenMetadata).isEqualTo(expectedTokenMetadata);
|
||||||
|
|
||||||
|
assertThat(tokens.getRefreshToken()).isEqualTo(this.refreshToken);
|
||||||
|
tokenMetadata = tokens.getTokenMetadata(tokens.getRefreshToken());
|
||||||
|
assertThat(tokenMetadata).isEqualTo(expectedTokenMetadata);
|
||||||
|
|
||||||
|
assertThat(tokens.getToken(OidcIdToken.class)).isEqualTo(this.idToken);
|
||||||
|
tokenMetadata = tokens.getTokenMetadata(tokens.getToken(OidcIdToken.class));
|
||||||
|
assertThat(tokenMetadata).isEqualTo(expectedTokenMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getTokenMetadataWhenTokenNotFoundThenNull() {
|
||||||
|
OAuth2TokenMetadata expectedTokenMetadata = OAuth2TokenMetadata.builder().build();
|
||||||
|
OAuth2Tokens tokens = OAuth2Tokens.builder()
|
||||||
|
.accessToken(this.accessToken, expectedTokenMetadata)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(tokens.getAccessToken()).isEqualTo(this.accessToken);
|
||||||
|
OAuth2TokenMetadata tokenMetadata = tokens.getTokenMetadata(tokens.getAccessToken());
|
||||||
|
assertThat(tokenMetadata).isEqualTo(expectedTokenMetadata);
|
||||||
|
|
||||||
|
OAuth2AccessToken otherAccessToken = new OAuth2AccessToken(
|
||||||
|
this.accessToken.getTokenType(),
|
||||||
|
"other-access-token",
|
||||||
|
this.accessToken.getIssuedAt(),
|
||||||
|
this.accessToken.getExpiresAt(),
|
||||||
|
this.accessToken.getScopes());
|
||||||
|
assertThat(tokens.getTokenMetadata(otherAccessToken)).isNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user