Compare commits
239 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b47d5ebb72 | ||
|
|
1a6a9dd1f2 | ||
|
|
836acf7c0e | ||
|
|
32c53e53da | ||
|
|
5bb43af3ff | ||
|
|
6dc3944eef | ||
|
|
4eb25c163f | ||
|
|
356d669a78 | ||
|
|
11ce8ef201 | ||
|
|
bfd7a09c3b | ||
|
|
efbfdc234c | ||
|
|
2ba711c83a | ||
|
|
8d7f8b3420 | ||
|
|
8c2b095195 | ||
|
|
b1b2bc438f | ||
|
|
72804be45b | ||
|
|
629e220c2f | ||
|
|
9c964e37b0 | ||
|
|
d7aa72af68 | ||
|
|
a9cf857d33 | ||
|
|
3729dc0d43 | ||
|
|
dc142cb253 | ||
|
|
9c0ca08e68 | ||
|
|
1f68ad1655 | ||
|
|
b71801cd1e | ||
|
|
213bf49510 | ||
|
|
f114e3a1ce | ||
|
|
17c882c06d | ||
|
|
92dbcf29a5 | ||
|
|
26aed3c183 | ||
|
|
cd6f1d7dc3 | ||
|
|
4d94e7095d | ||
|
|
80b01854f2 | ||
|
|
c326b1a2ba | ||
|
|
1db05991af | ||
|
|
2cc603c7e7 | ||
|
|
07d69cbfb4 | ||
|
|
502fa24cfb | ||
|
|
4466cbe69d | ||
|
|
8043b8c949 | ||
|
|
0994a1e1e1 | ||
|
|
70d433a45a | ||
|
|
2dabfa02e0 | ||
|
|
6b66719a83 | ||
|
|
aebc613862 | ||
|
|
f583668a9c | ||
|
|
3efee494ad | ||
|
|
c60ae4532f | ||
|
|
4066c3ec4d | ||
|
|
daeeb14141 | ||
|
|
6701913d0e | ||
|
|
03b31d90a7 | ||
|
|
c9ac9afb60 | ||
|
|
ce0d1b7b1e | ||
|
|
2e07ce8b8f | ||
|
|
61621c5671 | ||
|
|
532cade256 | ||
|
|
6284e1dc1e | ||
|
|
4934088593 | ||
|
|
0656fde051 | ||
|
|
83be809814 | ||
|
|
a38b66adf2 | ||
|
|
33a78913fa | ||
|
|
f29a068b7a | ||
|
|
94053f2068 | ||
|
|
0b85fdc883 | ||
|
|
852d795abc | ||
|
|
95e9a9e4bd | ||
|
|
403d7070d2 | ||
|
|
fdda88263b | ||
|
|
8aa2213bc4 | ||
|
|
af5f71f2ae | ||
|
|
6d69f3831e | ||
|
|
9c34eb874c | ||
|
|
05468b7b29 | ||
|
|
33f6f4f731 | ||
|
|
a48fbe46d5 | ||
|
|
0808fba26e | ||
|
|
2b2221a467 | ||
|
|
3877999a2a | ||
|
|
ab601640ff | ||
|
|
435a17e4b9 | ||
|
|
65400f8c71 | ||
|
|
f008d5dded | ||
|
|
d5a57396a4 | ||
|
|
19c7606c52 | ||
|
|
303043ea78 | ||
|
|
3f5e7c793b | ||
|
|
931aaa4e94 | ||
|
|
ec7ab5c956 | ||
|
|
fdf0a2f94c | ||
|
|
b37d4dd31e | ||
|
|
4199ab0172 | ||
|
|
7dfdcf3a27 | ||
|
|
0cae3c693e | ||
|
|
d6ff0f3fc7 | ||
|
|
c75b8a1cb9 | ||
|
|
77d665fe97 | ||
|
|
dc79172a4b | ||
|
|
e204b6bced | ||
|
|
d46bdfc80b | ||
|
|
421a9723ea | ||
|
|
d09e5348c4 | ||
|
|
5778192cbf | ||
|
|
2c1132c6ee | ||
|
|
991b641189 | ||
|
|
8ee1c3473a | ||
|
|
d5e822c7d4 | ||
|
|
a49a5dbcad | ||
|
|
4cf7dd538a | ||
|
|
ca2ffb0756 | ||
|
|
c4406cda67 | ||
|
|
d8421d5e3b | ||
|
|
ed786c541c | ||
|
|
2fc0c5efb8 | ||
|
|
7edbe2a30d | ||
|
|
5bd2968dac | ||
|
|
76212f1018 | ||
|
|
90e09594d7 | ||
|
|
7be02ceda0 | ||
|
|
4defb0ef87 | ||
|
|
8a18f20ba8 | ||
|
|
8a9ce86710 | ||
|
|
c2ba98ee7d | ||
|
|
c4a5015f8b | ||
|
|
bb1bd6b0b8 | ||
|
|
6bec78cf6b | ||
|
|
51d00742f3 | ||
|
|
9171006952 | ||
|
|
ecda4e0a15 | ||
|
|
14cedd7895 | ||
|
|
4443312739 | ||
|
|
eb0ac3fa16 | ||
|
|
170159cc2c | ||
|
|
4c54c5976f | ||
|
|
e687970f2b | ||
|
|
8bc7b77ed7 | ||
|
|
1a115793ac | ||
|
|
fc05e22bce | ||
|
|
9d744045ce | ||
|
|
abd376ef1e | ||
|
|
c86a5f9735 | ||
|
|
6dd34f3afb | ||
|
|
08d37771e3 | ||
|
|
d0bb94b887 | ||
|
|
b991e1adc1 | ||
|
|
627ae61785 | ||
|
|
b6f477b1d3 | ||
|
|
fff62db117 | ||
|
|
9c1d224843 | ||
|
|
f8d613c22b | ||
|
|
30c17a5e49 | ||
|
|
6470c71e77 | ||
|
|
5652e022ab | ||
|
|
6b0c2e3276 | ||
|
|
a1fefd937b | ||
|
|
47717a8163 | ||
|
|
1d34ceb05d | ||
|
|
71915331d8 | ||
|
|
a43abd4d5e | ||
|
|
8968187ea4 | ||
|
|
3cc89f925e | ||
|
|
38e37ae3eb | ||
|
|
8458d1249e | ||
|
|
a8974c6cca | ||
|
|
51de75bc01 | ||
|
|
b05bf74a82 | ||
|
|
a1e1f49606 | ||
|
|
2601dc7e79 | ||
|
|
4d2a675272 | ||
|
|
10859ffc09 | ||
|
|
3ec61890e1 | ||
|
|
005cd2a130 | ||
|
|
82e28a15f4 | ||
|
|
7845b6a369 | ||
|
|
72c2e15487 | ||
|
|
00faf44554 | ||
|
|
718b86c983 | ||
|
|
2c4639ce88 | ||
|
|
3e6f8ff42a | ||
|
|
a5c5b7ea43 | ||
|
|
06f1f3726d | ||
|
|
b1d5a7a1f8 | ||
|
|
166958364a | ||
|
|
16d1d871eb | ||
|
|
8354aaa3cc | ||
|
|
586c7daf2a | ||
|
|
12ae92b366 | ||
|
|
7160290aaf | ||
|
|
5ab82f83cb | ||
|
|
a13df8a1af | ||
|
|
3fe6f86659 | ||
|
|
ccf4a2de6e | ||
|
|
c2db8926df | ||
|
|
5b7d900424 | ||
|
|
c3402b0b12 | ||
|
|
32414451f5 | ||
|
|
a661e1cdb7 | ||
|
|
cdb48f510e | ||
|
|
c799261a72 | ||
|
|
8b32ace9e5 | ||
|
|
3812c0e56e | ||
|
|
cb1b23a5b2 | ||
|
|
d4f539cd7a | ||
|
|
609b4a56a9 | ||
|
|
0b11d88a62 | ||
|
|
37e68803b9 | ||
|
|
22ecfd267c | ||
|
|
4fbe06d121 | ||
|
|
58bac49f97 | ||
|
|
f8fdcd7ae9 | ||
|
|
66bc5a0e65 | ||
|
|
e175f4fda3 | ||
|
|
a1e513b35d | ||
|
|
4d5b288116 | ||
|
|
362c947df1 | ||
|
|
209248d96d | ||
|
|
104d273ba5 | ||
|
|
dce3f02f66 | ||
|
|
e73bef58a3 | ||
|
|
5412f10ff8 | ||
|
|
4081d460a2 | ||
|
|
30d7846122 | ||
|
|
1476676cec | ||
|
|
165d290374 | ||
|
|
9614a252c9 | ||
|
|
d302444650 | ||
|
|
1370f7e51a | ||
|
|
36d18312b0 | ||
|
|
7b333150a2 | ||
|
|
a20f321a19 | ||
|
|
525eca63d2 | ||
|
|
edd7cf2434 | ||
|
|
f1a01597d9 | ||
|
|
16e4f5130b | ||
|
|
13d3567eb4 | ||
|
|
a846e936e9 | ||
|
|
d0e1107f36 | ||
|
|
42095a6da5 |
@@ -1,54 +1,89 @@
|
||||
name: CI
|
||||
name: Build and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- '**'
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
|
||||
env:
|
||||
RUN_JOBS: ${{ github.repository == 'spring-projects/spring-authorization-server' }}
|
||||
|
||||
jobs:
|
||||
prerequisites:
|
||||
name: Pre-requisites for building
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
runjobs: ${{ steps.continue.outputs.runjobs }}
|
||||
project_version: ${{ steps.continue.outputs.project_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- id: continue
|
||||
name: Determine if should continue
|
||||
if: env.RUN_JOBS == 'true'
|
||||
run: |
|
||||
# Run jobs if in upstream repository
|
||||
echo "::set-output name=runjobs::true"
|
||||
# Extract version from gradle.properties
|
||||
version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
|
||||
echo "::set-output name=project_version::$version"
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [prerequisites]
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
jdk: [8,11,12]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
jdk: [8,11,17]
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK ${{ matrix.jdk }}
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: ${{ matrix.jdk }}
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
env:
|
||||
GRADLE_USER_HOME: ~/.gradle
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew clean build
|
||||
env:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
run: ./gradlew clean build --continue -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD"
|
||||
snapshot_tests:
|
||||
name: Test against snapshots
|
||||
needs: [prerequisites]
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
- name: Test
|
||||
run: echo Testing against snapshots
|
||||
sonar:
|
||||
name: Static Code Analysis
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
- name: Sonar
|
||||
run: echo Running Sonarqube static code analysis
|
||||
artifacts:
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Snapshot Tests
|
||||
env:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
run: ./gradlew test --refresh-dependencies -Duser.name=spring-builds+github -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PspringFrameworkVersion='5.3.+' -PspringSecurityVersion='5.8.+' -PlocksDisabled --stacktrace
|
||||
deploy_artifacts:
|
||||
name: Deploy Artifacts
|
||||
needs: [build, snapshot_tests, sonar]
|
||||
needs: [build, snapshot_tests]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -56,11 +91,23 @@ jobs:
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Deploy Artifacts
|
||||
run: echo Deploying Artifacts
|
||||
docs:
|
||||
env:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSPHRASE }}
|
||||
OSSRH_TOKEN_USERNAME: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }}
|
||||
OSSRH_TOKEN_PASSWORD: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }}
|
||||
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
run: ./gradlew publishArtifacts finalizeDeployArtifacts -Duser.name=spring-builds+github -PossrhUsername="$OSSRH_TOKEN_USERNAME" -PossrhPassword="$OSSRH_TOKEN_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace
|
||||
deploy_docs:
|
||||
name: Deploy Docs
|
||||
needs: [build, snapshot_tests, sonar]
|
||||
needs: [build, snapshot_tests]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -68,17 +115,14 @@ jobs:
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Deploy Docs
|
||||
run: echo Deploying Docs
|
||||
schema:
|
||||
name: Deploy Schema
|
||||
needs: [build, snapshot_tests, sonar]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
- name: Deploy Schema
|
||||
run: echo Deploying Schema
|
||||
env:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }}
|
||||
DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }}
|
||||
DOCS_HOST: ${{ secrets.DOCS_HOST }}
|
||||
run: ./gradlew deployDocs -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost="$DOCS_HOST" --stacktrace
|
||||
|
||||
6
.github/workflows/pr-build-workflow.yml
vendored
6
.github/workflows/pr-build-workflow.yml
vendored
@@ -3,7 +3,7 @@ name: PR build
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -11,8 +11,8 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
jdk: [8,11,12]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
jdk: [8]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -18,7 +18,7 @@ Ideally, that would include a https://stackoverflow.com/help/minimal-reproducibl
|
||||
|
||||
== Submitting Pull Requests
|
||||
This project uses https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests[pull requests] for the community to suggest changes to the project.
|
||||
There are a few imporant things to keep in mind when submitting a pull request:
|
||||
There are a few important things to keep in mind when submitting a pull request:
|
||||
|
||||
* Expect feedback and to make changes to your contributions.
|
||||
* Unless it is a minor change:
|
||||
|
||||
105
Jenkinsfile
vendored
105
Jenkinsfile
vendored
@@ -1,105 +0,0 @@
|
||||
def projectProperties = [
|
||||
[$class: 'BuildDiscarderProperty',
|
||||
strategy: [$class: 'LogRotator', numToKeepStr: '5']],
|
||||
pipelineTriggers([cron('@daily')])
|
||||
]
|
||||
properties(projectProperties)
|
||||
|
||||
def SUCCESS = hudson.model.Result.SUCCESS.toString()
|
||||
currentBuild.result = SUCCESS
|
||||
|
||||
def GRADLE_ENTERPRISE_CACHE_USER = usernamePassword(credentialsId: 'gradle_enterprise_cache_user',
|
||||
passwordVariable: 'GRADLE_ENTERPRISE_CACHE_PASSWORD',
|
||||
usernameVariable: 'GRADLE_ENTERPRISE_CACHE_USERNAME')
|
||||
def GRADLE_ENTERPRISE_SECRET_ACCESS_KEY = string(credentialsId: 'gradle_enterprise_secret_access_key',
|
||||
variable: 'GRADLE_ENTERPRISE_ACCESS_KEY')
|
||||
def SPRING_SIGNING_SECRING = file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')
|
||||
def SPRING_GPG_PASSPHRASE = string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')
|
||||
def OSSRH_S01_CREDENTIALS = usernamePassword(credentialsId: 'oss-s01-token', passwordVariable: 'OSSRH_S01_TOKEN_PASSWORD', usernameVariable: 'OSSRH_S01_TOKEN_USERNAME')
|
||||
def ARTIFACTORY_CREDENTIALS = usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')
|
||||
def JENKINS_PRIVATE_SSH_KEY = file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')
|
||||
def SONAR_LOGIN_CREDENTIALS = string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')
|
||||
def JENKINS_USER = '-Duser.name="spring-builds+jenkins"'
|
||||
|
||||
def jdkEnv(String jdk = 'jdk8') {
|
||||
def jdkTool = tool(jdk)
|
||||
return "JAVA_HOME=${ jdkTool }"
|
||||
}
|
||||
|
||||
try {
|
||||
parallel check: {
|
||||
stage('Check') {
|
||||
node {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS,
|
||||
GRADLE_ENTERPRISE_CACHE_USER,
|
||||
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY]) {
|
||||
withEnv([jdkEnv(),
|
||||
"GRADLE_ENTERPRISE_CACHE_USERNAME=${GRADLE_ENTERPRISE_CACHE_USERNAME}",
|
||||
"GRADLE_ENTERPRISE_CACHE_PASSWORD=${GRADLE_ENTERPRISE_CACHE_PASSWORD}",
|
||||
"GRADLE_ENTERPRISE_ACCESS_KEY=${GRADLE_ENTERPRISE_ACCESS_KEY}"]) {
|
||||
sh "./gradlew $JENKINS_USER check -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --stacktrace"
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
currentBuild.result = 'FAILED: check'
|
||||
throw e
|
||||
} finally {
|
||||
junit '**/build/test-results/*/*.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(currentBuild.result == 'SUCCESS') {
|
||||
parallel artifacts: {
|
||||
stage('Deploy Artifacts') {
|
||||
node {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
withCredentials([SPRING_SIGNING_SECRING,
|
||||
SPRING_GPG_PASSPHRASE,
|
||||
OSSRH_S01_CREDENTIALS,
|
||||
ARTIFACTORY_CREDENTIALS,
|
||||
GRADLE_ENTERPRISE_CACHE_USER,
|
||||
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY]) {
|
||||
withEnv([jdkEnv(),
|
||||
"GRADLE_ENTERPRISE_CACHE_USERNAME=${GRADLE_ENTERPRISE_CACHE_USERNAME}",
|
||||
"GRADLE_ENTERPRISE_CACHE_PASSWORD=${GRADLE_ENTERPRISE_CACHE_PASSWORD}",
|
||||
"GRADLE_ENTERPRISE_ACCESS_KEY=${GRADLE_ENTERPRISE_ACCESS_KEY}"]) {
|
||||
sh "./gradlew $JENKINS_USER deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhTokenUsername=$OSSRH_S01_TOKEN_USERNAME -PossrhTokenPassword=$OSSRH_S01_TOKEN_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --stacktrace"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
currentBuild.result = 'FAILED: deploys'
|
||||
throw e
|
||||
} finally {
|
||||
def buildStatus = currentBuild.result
|
||||
def buildNotSuccess = !SUCCESS.equals(buildStatus)
|
||||
def lastBuildNotSuccess = !SUCCESS.equals(currentBuild.previousBuild?.result)
|
||||
|
||||
if(buildNotSuccess || lastBuildNotSuccess) {
|
||||
|
||||
stage('Notifiy') {
|
||||
node {
|
||||
final def RECIPIENTS = [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']]
|
||||
|
||||
def subject = "${buildStatus}: Build ${env.JOB_NAME} ${env.BUILD_NUMBER} status is now ${buildStatus}"
|
||||
def details = """The build status changed to ${buildStatus}. For details see ${env.BUILD_URL}"""
|
||||
|
||||
emailext (
|
||||
subject: subject,
|
||||
body: details,
|
||||
recipientProviders: RECIPIENTS,
|
||||
to: "$SPRING_SECURITY_TEAM_EMAILS"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ image:https://github.com/spring-projects/spring-authorization-server/workflows/C
|
||||
|
||||
= Spring Authorization Server
|
||||
|
||||
The Spring Authorization Server project, led by the https://spring.io/projects/spring-security/[Spring Security] team, is focused on delivering https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-1.1[OAuth 2.1 Authorization Server] support to the Spring community.
|
||||
The Spring Authorization Server project, led by the https://spring.io/projects/spring-security/[Spring Security] team, is focused on delivering https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-06#section-1.1[OAuth 2.1 Authorization Server] support to the Spring community.
|
||||
|
||||
This project replaces the Authorization Server support provided by https://spring.io/projects/spring-security-oauth/[Spring Security OAuth].
|
||||
|
||||
@@ -13,7 +13,7 @@ This project uses https://www.zenhub.com/[ZenHub] to prioritize the feature road
|
||||
The project board can be accessed https://app.zenhub.com/workspaces/authorization-server-5e8f3182b5e8f5841bfc4902/board?repos=248032165[here].
|
||||
It is recommended to install the ZenHub https://www.zenhub.com/extension[browser extension] as it integrates natively within GitHub's user interface.
|
||||
|
||||
The completed and upcoming feature list can be viewed in the https://github.com/spring-projects/spring-authorization-server/wiki/Feature-List[wiki].
|
||||
The feature list can be viewed in the https://docs.spring.io/spring-authorization-server/docs/current/reference/html/overview.html#feature-list[reference documentation].
|
||||
|
||||
== Support Policy
|
||||
The Spring Authorization Server project provides software support and is documented in its link:SUPPORT_POLICY.adoc[support policy].
|
||||
@@ -35,9 +35,9 @@ The goal is to leverage all the knowledge learned thus far and apply the same to
|
||||
Submitted work via pull requests should follow the same coding style/conventions and adopt the same or similar design patterns that have been established in Spring Security's OAuth 2.0 support.
|
||||
|
||||
== Documentation
|
||||
Be sure to read the https://docs.spring.io/spring-security/reference[Spring Security Reference], as well as the https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html[OAuth 2.0 Reference], which describes the Client and Resource Server features available.
|
||||
Be sure to read the https://docs.spring.io/spring-authorization-server/docs/current/reference/html/[Spring Authorization Server Reference] and https://docs.spring.io/spring-security/reference[Spring Security Reference], as well as the https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html[OAuth 2.0 Reference], which describes the Client and Resource Server features available.
|
||||
|
||||
Extensive JavaDoc for the Spring Security code is also available in the https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API Documentation].
|
||||
JavaDoc is also available for the https://docs.spring.io/spring-authorization-server/docs/current/api/[Spring Authorization Server API] and https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API].
|
||||
|
||||
== Code of Conduct
|
||||
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].
|
||||
|
||||
50
build.gradle
50
build.gradle
@@ -1,47 +1,13 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath 'io.spring.gradle:spring-build-conventions:0.0.38'
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
|
||||
classpath 'io.spring.nohttp:nohttp-gradle:0.0.8'
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
url = 'https://repo.spring.io/plugins-snapshot'
|
||||
if (project.hasProperty('artifactoryUsername')) {
|
||||
credentials {
|
||||
username "$artifactoryUsername"
|
||||
password "$artifactoryPassword"
|
||||
}
|
||||
}
|
||||
}
|
||||
maven { url 'https://plugins.gradle.org/m2/' }
|
||||
}
|
||||
plugins {
|
||||
id "io.spring.convention.root"
|
||||
}
|
||||
|
||||
apply plugin: 'io.spring.nohttp'
|
||||
apply plugin: 'locks'
|
||||
apply plugin: 'io.spring.convention.root'
|
||||
group = "org.springframework.security"
|
||||
description = "Spring Authorization Server"
|
||||
|
||||
group = 'org.springframework.security'
|
||||
description = 'Spring Authorization Server'
|
||||
|
||||
ext.snapshotBuild = version.contains("SNAPSHOT")
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencyManagementExport.projects = subprojects.findAll { !it.name.contains('-boot') }
|
||||
|
||||
subprojects {
|
||||
plugins.withType(JavaPlugin) {
|
||||
project.sourceCompatibility = "1.8"
|
||||
}
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = "UTF-8"
|
||||
if (hasProperty("buildScan")) {
|
||||
buildScan {
|
||||
termsOfServiceUrl = "https://gradle.com/terms-of-service"
|
||||
termsOfServiceAgree = "yes"
|
||||
}
|
||||
}
|
||||
|
||||
nohttp {
|
||||
allowlistFile = project.file("etc/nohttp/allowlist.lines")
|
||||
}
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
apply plugin: "java-gradle-plugin"
|
||||
plugins {
|
||||
id "java-gradle-plugin"
|
||||
id "java"
|
||||
id "groovy"
|
||||
}
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
maven { url "https://repo.spring.io/plugins-release/" }
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
locks {
|
||||
id = "locks"
|
||||
implementationClass = "lock.GlobalLockPlugin"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation "com.github.ben-manes:gradle-versions-plugin:0.38.0"
|
||||
implementation "io.github.gradle-nexus:publish-plugin:1.1.0"
|
||||
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:0.0.31"
|
||||
implementation "io.spring.nohttp:nohttp-gradle:0.0.10"
|
||||
implementation "org.asciidoctor:asciidoctor-gradle-jvm:3.3.2"
|
||||
implementation "org.asciidoctor:asciidoctor-gradle-jvm-pdf:3.3.2"
|
||||
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
|
||||
implementation "org.hidetake:gradle-ssh-plugin:2.10.1"
|
||||
implementation "org.jfrog.buildinfo:build-info-extractor-gradle:4.26.1"
|
||||
implementation "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1"
|
||||
implementation "org.springframework:spring-core:5.3.23"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.docs
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
|
||||
class SpringDeployDocsPlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
void apply(Project project) {
|
||||
project.getPluginManager().apply('org.hidetake.ssh')
|
||||
|
||||
project.ssh.settings {
|
||||
knownHosts = allowAnyHosts
|
||||
}
|
||||
project.remotes {
|
||||
docs {
|
||||
role 'docs'
|
||||
if (project.hasProperty('deployDocsHost')) {
|
||||
host = project.findProperty('deployDocsHost')
|
||||
}
|
||||
retryCount = 5 // retry 5 times (default is 0)
|
||||
retryWaitSec = 10 // wait 10 seconds between retries (default is 0)
|
||||
user = project.findProperty('deployDocsSshUsername')
|
||||
if (project.hasProperty('deployDocsSshKeyPath')) {
|
||||
identity = project.file(project.findProperty('deployDocsSshKeyPath'))
|
||||
} else if (project.hasProperty('deployDocsSshKey')) {
|
||||
identity = project.findProperty('deployDocsSshKey')
|
||||
}
|
||||
if (project.hasProperty('deployDocsSshPassphrase')) {
|
||||
passphrase = project.findProperty('deployDocsSshPassphrase')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project.task('deployDocs') {
|
||||
dependsOn 'docsZip'
|
||||
doFirst {
|
||||
project.ssh.run {
|
||||
session(project.remotes.docs) {
|
||||
def now = System.currentTimeMillis()
|
||||
def name = project.rootProject.name
|
||||
def version = project.rootProject.version
|
||||
def tempPath = "/tmp/${name}-${now}-docs/".replaceAll(' ', '_')
|
||||
execute "mkdir -p $tempPath"
|
||||
|
||||
project.tasks.docsZip.outputs.each { o ->
|
||||
put from: o.files, into: tempPath
|
||||
}
|
||||
|
||||
execute "unzip $tempPath*.zip -d $tempPath"
|
||||
|
||||
def extractPath = "/var/www/domains/spring.io/docs/htdocs/autorepo/docs/${name}/${version}/"
|
||||
|
||||
execute "rm -rf $extractPath"
|
||||
execute "mkdir -p $extractPath"
|
||||
execute "mv $tempPath/docs/* $extractPath"
|
||||
execute "chmod -R g+w $extractPath"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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 io.spring.gradle.convention;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.file.DuplicatesStrategy;
|
||||
import org.gradle.api.plugins.BasePlugin;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.PluginManager;
|
||||
import org.gradle.api.tasks.TaskContainer;
|
||||
import org.gradle.api.tasks.bundling.Zip;
|
||||
|
||||
import org.springframework.gradle.docs.SpringAsciidoctorPlugin;
|
||||
import org.springframework.gradle.docs.SpringJavadocApiPlugin;
|
||||
import org.springframework.gradle.docs.SpringJavadocOptionsPlugin;
|
||||
import org.springframework.gradle.management.SpringManagementConfigurationPlugin;
|
||||
import org.springframework.gradle.maven.SpringRepositoryPlugin;
|
||||
|
||||
/**
|
||||
* Aggregates asciidoc, javadoc, and deploying of the docs into a single plugin.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringDocsPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
// Apply default plugins
|
||||
PluginManager pluginManager = project.getPluginManager();
|
||||
pluginManager.apply(BasePlugin.class);
|
||||
pluginManager.apply(JavaPlugin.class);
|
||||
pluginManager.apply(SpringManagementConfigurationPlugin.class);
|
||||
pluginManager.apply(SpringRepositoryPlugin.class);
|
||||
pluginManager.apply(SpringAsciidoctorPlugin.class);
|
||||
// Note: Applying plugin via id since it requires groovy compilation
|
||||
pluginManager.apply("org.springframework.gradle.deploy-docs");
|
||||
pluginManager.apply(SpringJavadocApiPlugin.class);
|
||||
pluginManager.apply(SpringJavadocOptionsPlugin.class);
|
||||
|
||||
TaskContainer tasks = project.getTasks();
|
||||
project.configure(tasks.withType(AbstractAsciidoctorTask.class), (task) -> {
|
||||
File destination = new File(project.getBuildDir(), "docs");
|
||||
task.setOutputDir(destination);
|
||||
task.sources((patternSet) -> {
|
||||
patternSet.include("**/*.adoc");
|
||||
patternSet.exclude("_*/**");
|
||||
});
|
||||
});
|
||||
|
||||
// Add task to create documentation archive
|
||||
Zip docsZip = tasks.create("docsZip", Zip.class, (zip) -> {
|
||||
zip.dependsOn(tasks.getByName("api"), tasks.getByName("asciidoctor")/*, tasks.getByName("asciidoctorPdf")*/);
|
||||
zip.setGroup("Distribution");
|
||||
zip.getArchiveBaseName().set(project.getRootProject().getName());
|
||||
zip.getArchiveClassifier().set("docs");
|
||||
zip.setDescription("Builds -docs archive containing all " +
|
||||
"Docs for deployment at docs.spring.io");
|
||||
zip.into("docs");
|
||||
zip.setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE);
|
||||
});
|
||||
|
||||
// Add task to aggregate documentation
|
||||
Task docs = tasks.create("docs", (task) -> {
|
||||
task.dependsOn(docsZip);
|
||||
task.setGroup("Documentation");
|
||||
task.setDescription("An aggregator task to generate all the documentation");
|
||||
});
|
||||
|
||||
// Wire docs task into the build
|
||||
tasks.getByName("assemble").dependsOn(docs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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 io.spring.gradle.convention;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaLibraryPlugin;
|
||||
import org.gradle.api.plugins.PluginManager;
|
||||
|
||||
import org.springframework.gradle.SpringJavaPlugin;
|
||||
import org.springframework.gradle.SpringMavenPlugin;
|
||||
import org.springframework.gradle.classpath.SpringCheckClasspathForProhibitedDependenciesPlugin;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringModulePlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
// Apply default plugins
|
||||
PluginManager pluginManager = project.getPluginManager();
|
||||
pluginManager.apply(JavaLibraryPlugin.class);
|
||||
pluginManager.apply(SpringJavaPlugin.class);
|
||||
pluginManager.apply(SpringMavenPlugin.class);
|
||||
pluginManager.apply(SpringCheckClasspathForProhibitedDependenciesPlugin.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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 io.spring.gradle.convention;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.BasePlugin;
|
||||
import org.gradle.api.plugins.PluginManager;
|
||||
|
||||
import org.springframework.gradle.classpath.SpringCheckProhibitedDependenciesLifecyclePlugin;
|
||||
import org.springframework.gradle.maven.SpringNexusPlugin;
|
||||
import org.springframework.gradle.nohttp.SpringNoHttpPlugin;
|
||||
import org.springframework.gradle.sonarqube.SpringSonarQubePlugin;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringRootProjectPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
// Apply default plugins
|
||||
PluginManager pluginManager = project.getPluginManager();
|
||||
pluginManager.apply(BasePlugin.class);
|
||||
pluginManager.apply(SpringNoHttpPlugin.class);
|
||||
pluginManager.apply(SpringNexusPlugin.class);
|
||||
pluginManager.apply(SpringCheckProhibitedDependenciesLifecyclePlugin.class);
|
||||
pluginManager.apply(SpringSonarQubePlugin.class);
|
||||
|
||||
// Apply default repositories
|
||||
project.getRepositories().mavenCentral();
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package lock;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class GlobalLockPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getTasks().register("writeLocks", GlobalLockTask.class, writeAll -> {
|
||||
writeAll.setDescription("Writes the locks for all projects");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package lock;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class GlobalLockTask extends DefaultTask {
|
||||
@TaskAction
|
||||
public void lock() {
|
||||
Project taskProject = getProject();
|
||||
if (!taskProject.getGradle().getStartParameter().isWriteDependencyLocks()) {
|
||||
throw new IllegalStateException("You just specify --write-locks argument");
|
||||
}
|
||||
writeLocksFor(taskProject);
|
||||
taskProject.getSubprojects().forEach(new Consumer<Project>() {
|
||||
@Override
|
||||
public void accept(Project subproject) {
|
||||
writeLocksFor(subproject);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void writeLocksFor(Project project) {
|
||||
project.getConfigurations().configureEach(new Action<Configuration>() {
|
||||
@Override
|
||||
public void execute(Configuration configuration) {
|
||||
if (configuration.isCanBeResolved()) {
|
||||
configuration.resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class ProjectUtils {
|
||||
private ProjectUtils() {
|
||||
}
|
||||
|
||||
public static String getProjectName(Project project) {
|
||||
String projectName = project.getRootProject().getName();
|
||||
if (projectName.endsWith("-build")) {
|
||||
projectName = projectName.substring(0, projectName.length() - "-build".length());
|
||||
}
|
||||
return projectName;
|
||||
}
|
||||
|
||||
public static boolean isSnapshot(Project project) {
|
||||
String projectVersion = projectVersion(project);
|
||||
return projectVersion.matches("^.*([.-]BUILD)?-SNAPSHOT$");
|
||||
}
|
||||
|
||||
public static boolean isMilestone(Project project) {
|
||||
String projectVersion = projectVersion(project);
|
||||
return projectVersion.matches("^.*[.-]M\\d+$") || projectVersion.matches("^.*[.-]RC\\d+$");
|
||||
}
|
||||
|
||||
public static boolean isRelease(Project project) {
|
||||
return !(isSnapshot(project) || isMilestone(project));
|
||||
}
|
||||
|
||||
private static String projectVersion(Project project) {
|
||||
return String.valueOf(project.getVersion());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.spring.javaformat.gradle.SpringJavaFormatPlugin;
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.GroovyPlugin;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.plugins.PluginManager;
|
||||
import org.gradle.api.tasks.compile.CompileOptions;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.jvm.tasks.Jar;
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper;
|
||||
|
||||
import org.springframework.gradle.checkstyle.SpringJavaCheckstylePlugin;
|
||||
import org.springframework.gradle.docs.SpringJavadocOptionsPlugin;
|
||||
import org.springframework.gradle.jacoco.SpringJacocoPlugin;
|
||||
import org.springframework.gradle.management.SpringManagementConfigurationPlugin;
|
||||
import org.springframework.gradle.maven.SpringRepositoryPlugin;
|
||||
import org.springframework.gradle.propdeps.SpringPropDepsEclipsePlugin;
|
||||
import org.springframework.gradle.propdeps.SpringPropDepsIdeaPlugin;
|
||||
import org.springframework.gradle.properties.SpringCopyPropertiesPlugin;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringJavaPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
// Apply default plugins
|
||||
PluginManager pluginManager = project.getPluginManager();
|
||||
pluginManager.apply(JavaPlugin.class);
|
||||
pluginManager.apply(SpringManagementConfigurationPlugin.class);
|
||||
if (project.file("src/main/groovy").exists()
|
||||
|| project.file("src/test/groovy").exists()
|
||||
|| project.file("src/integration-test/groovy").exists()) {
|
||||
pluginManager.apply(GroovyPlugin.class);
|
||||
}
|
||||
if (project.file("src/main/kotlin").exists()
|
||||
|| project.file("src/test/kotlin").exists()
|
||||
|| project.file("src/integration-test/kotlin").exists()
|
||||
|| project.getBuildFile().getName().endsWith(".kts")) {
|
||||
pluginManager.apply(KotlinPluginWrapper.class);
|
||||
}
|
||||
pluginManager.apply(SpringRepositoryPlugin.class);
|
||||
pluginManager.apply(SpringPropDepsEclipsePlugin.class);
|
||||
pluginManager.apply(SpringPropDepsIdeaPlugin.class);
|
||||
pluginManager.apply(SpringJavadocOptionsPlugin.class);
|
||||
pluginManager.apply(SpringJavaFormatPlugin.class);
|
||||
pluginManager.apply(SpringJavaCheckstylePlugin.class);
|
||||
pluginManager.apply(SpringCopyPropertiesPlugin.class);
|
||||
pluginManager.apply(SpringJacocoPlugin.class);
|
||||
|
||||
// Apply Java source compatibility version
|
||||
JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class);
|
||||
java.setTargetCompatibility(JavaVersion.VERSION_1_8);
|
||||
|
||||
// Configure Java tasks
|
||||
project.getTasks().withType(JavaCompile.class, (javaCompile) -> {
|
||||
CompileOptions options = javaCompile.getOptions();
|
||||
options.setEncoding("UTF-8");
|
||||
options.getCompilerArgs().add("-parameters");
|
||||
if (JavaVersion.current().isJava11Compatible()) {
|
||||
options.getRelease().set(8);
|
||||
}
|
||||
});
|
||||
project.getTasks().withType(Jar.class, (jar) -> jar.manifest((manifest) -> {
|
||||
Map<String, String> attributes = new HashMap<>();
|
||||
attributes.put("Created-By", String.format("%s (%s)", System.getProperty("java.version"), System.getProperty("java.specification.vendor")));
|
||||
attributes.put("Implementation-Title", project.getName());
|
||||
attributes.put("Implementation-Version", project.getVersion().toString());
|
||||
attributes.put("Automatic-Module-Name", project.getName().replace("-", "."));
|
||||
manifest.attributes(attributes);
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.PluginManager;
|
||||
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
|
||||
|
||||
import org.springframework.gradle.maven.SpringArtifactoryPlugin;
|
||||
import org.springframework.gradle.maven.SpringMavenPublishingConventionsPlugin;
|
||||
import org.springframework.gradle.maven.SpringPublishAllJavaComponentsPlugin;
|
||||
import org.springframework.gradle.maven.SpringPublishArtifactsPlugin;
|
||||
import org.springframework.gradle.maven.SpringPublishLocalPlugin;
|
||||
import org.springframework.gradle.maven.SpringSigningPlugin;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringMavenPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
// Apply default plugins
|
||||
PluginManager pluginManager = project.getPluginManager();
|
||||
pluginManager.apply(MavenPublishPlugin.class);
|
||||
|
||||
pluginManager.apply(SpringSigningPlugin.class);
|
||||
pluginManager.apply(SpringMavenPublishingConventionsPlugin.class);
|
||||
pluginManager.apply(SpringPublishAllJavaComponentsPlugin.class);
|
||||
pluginManager.apply(SpringPublishLocalPlugin.class);
|
||||
pluginManager.apply(SpringPublishArtifactsPlugin.class);
|
||||
pluginManager.apply(SpringArtifactoryPlugin.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.checkstyle;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import io.spring.javaformat.gradle.tasks.CheckFormat;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.quality.CheckstyleExtension;
|
||||
import org.gradle.api.plugins.quality.CheckstylePlugin;
|
||||
|
||||
/**
|
||||
* Adds and configures Checkstyle plugin.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringJavaCheckstylePlugin implements Plugin<Project> {
|
||||
private static final String CHECKSTYLE_DIR = "etc/checkstyle";
|
||||
private static final String SPRING_JAVAFORMAT_VERSION_PROPERTY = "springJavaformatVersion";
|
||||
private static final String DEFAULT_SPRING_JAVAFORMAT_VERSION = "0.0.31";
|
||||
private static final String NOHTTP_CHECKSTYLE_VERSION_PROPERTY = "nohttpCheckstyleVersion";
|
||||
private static final String DEFAULT_NOHTTP_CHECKSTYLE_VERSION = "0.0.10";
|
||||
private static final String CHECKSTYLE_TOOL_VERSION_PROPERTY = "checkstyleToolVersion";
|
||||
private static final String DEFAULT_CHECKSTYLE_TOOL_VERSION = "8.34";
|
||||
private static final String SPRING_JAVAFORMAT_EXCLUDE_PACKAGES_PROPERTY = "springJavaformatExcludePackages";
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
|
||||
File checkstyleDir = project.getRootProject().file(CHECKSTYLE_DIR);
|
||||
if (checkstyleDir.exists() && checkstyleDir.isDirectory()) {
|
||||
project.getPluginManager().apply(CheckstylePlugin.class);
|
||||
|
||||
// NOTE: See gradle.properties#springJavaformatVersion for actual version number
|
||||
project.getDependencies().add("checkstyle", "io.spring.javaformat:spring-javaformat-checkstyle:" + getSpringJavaformatVersion(project));
|
||||
// NOTE: See gradle.properties#nohttpCheckstyleVersion for actual version number
|
||||
project.getDependencies().add("checkstyle", "io.spring.nohttp:nohttp-checkstyle:" + getNohttpCheckstyleVersion(project));
|
||||
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.getConfigDirectory().set(checkstyleDir);
|
||||
// NOTE: See gradle.properties#checkstyleToolVersion for actual version number
|
||||
checkstyle.setToolVersion(getCheckstyleToolVersion(project));
|
||||
}
|
||||
|
||||
// Configure checkFormat task
|
||||
project.getTasks().withType(CheckFormat.class, (checkFormat) -> {
|
||||
// NOTE: See gradle.properties#springJavaformatExcludePackages for excluded packages
|
||||
String[] springJavaformatExcludePackages = getSpringJavaformatExcludePackages(project);
|
||||
if (springJavaformatExcludePackages != null) {
|
||||
checkFormat.exclude(springJavaformatExcludePackages);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static String getSpringJavaformatVersion(Project project) {
|
||||
String springJavaformatVersion = DEFAULT_SPRING_JAVAFORMAT_VERSION;
|
||||
if (project.hasProperty(SPRING_JAVAFORMAT_VERSION_PROPERTY)) {
|
||||
springJavaformatVersion = Objects.requireNonNull(project.findProperty(SPRING_JAVAFORMAT_VERSION_PROPERTY)).toString();
|
||||
}
|
||||
return springJavaformatVersion;
|
||||
}
|
||||
|
||||
private static String getNohttpCheckstyleVersion(Project project) {
|
||||
String nohttpCheckstyleVersion = DEFAULT_NOHTTP_CHECKSTYLE_VERSION;
|
||||
if (project.hasProperty(NOHTTP_CHECKSTYLE_VERSION_PROPERTY)) {
|
||||
nohttpCheckstyleVersion = Objects.requireNonNull(project.findProperty(NOHTTP_CHECKSTYLE_VERSION_PROPERTY)).toString();
|
||||
}
|
||||
return nohttpCheckstyleVersion;
|
||||
}
|
||||
|
||||
private static String getCheckstyleToolVersion(Project project) {
|
||||
String checkstyleToolVersion = DEFAULT_CHECKSTYLE_TOOL_VERSION;
|
||||
if (project.hasProperty(CHECKSTYLE_TOOL_VERSION_PROPERTY)) {
|
||||
checkstyleToolVersion = Objects.requireNonNull(project.findProperty(CHECKSTYLE_TOOL_VERSION_PROPERTY)).toString();
|
||||
}
|
||||
return checkstyleToolVersion;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String[] getSpringJavaformatExcludePackages(Project project) {
|
||||
String springJavaformatExcludePackages = (String) project.findProperty(SPRING_JAVAFORMAT_EXCLUDE_PACKAGES_PROPERTY);
|
||||
return (springJavaformatExcludePackages != null) ? springJavaformatExcludePackages.split(" ") : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.gradle.classpath;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ModuleVersionIdentifier;
|
||||
import org.gradle.api.artifacts.ResolvedConfiguration;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.tasks.Classpath;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
/**
|
||||
* A {@link Task} for checking the classpath for prohibited dependencies.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class CheckClasspathForProhibitedDependencies extends DefaultTask {
|
||||
|
||||
private Configuration classpath;
|
||||
|
||||
public CheckClasspathForProhibitedDependencies() {
|
||||
getOutputs().upToDateWhen((task) -> true);
|
||||
}
|
||||
|
||||
public void setClasspath(Configuration classpath) {
|
||||
this.classpath = classpath;
|
||||
}
|
||||
|
||||
@Classpath
|
||||
public FileCollection getClasspath() {
|
||||
return this.classpath;
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void checkForProhibitedDependencies() throws IOException {
|
||||
ResolvedConfiguration resolvedConfiguration = this.classpath.getResolvedConfiguration();
|
||||
TreeSet<String> prohibited = resolvedConfiguration.getResolvedArtifacts().stream()
|
||||
.map((artifact) -> artifact.getModuleVersion().getId()).filter(this::prohibited)
|
||||
.map((id) -> id.getGroup() + ":" + id.getName()).collect(Collectors.toCollection(TreeSet::new));
|
||||
if (!prohibited.isEmpty()) {
|
||||
StringBuilder message = new StringBuilder(String.format("Found prohibited dependencies in '%s':%n", this.classpath.getName()));
|
||||
for (String dependency : prohibited) {
|
||||
message.append(String.format(" %s%n", dependency));
|
||||
}
|
||||
throw new GradleException(message.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean prohibited(ModuleVersionIdentifier id) {
|
||||
String group = id.getGroup();
|
||||
if (group.equals("javax.batch")) {
|
||||
return false;
|
||||
}
|
||||
if (group.equals("javax.cache")) {
|
||||
return false;
|
||||
}
|
||||
if (group.equals("javax.money")) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Uncomment the following lines when upgrading to Spring Framework 6
|
||||
// if (group.startsWith("javax")) {
|
||||
// return true;
|
||||
// }
|
||||
if (group.equals("commons-logging")) {
|
||||
return true;
|
||||
}
|
||||
if (group.equals("org.slf4j") && id.getName().equals("jcl-over-slf4j")) {
|
||||
return true;
|
||||
}
|
||||
if (group.startsWith("org.jboss.spec")) {
|
||||
return true;
|
||||
}
|
||||
if (group.equals("org.apache.geronimo.specs")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.gradle.classpath;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.tasks.SourceSetContainer;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Andy Wilkinson
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class SpringCheckClasspathForProhibitedDependenciesPlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().apply(SpringCheckProhibitedDependenciesLifecyclePlugin.class);
|
||||
project.getPlugins().withType(JavaBasePlugin.class, (javaBasePlugin) ->
|
||||
configureProhibitedDependencyChecks(project));
|
||||
}
|
||||
|
||||
private void configureProhibitedDependencyChecks(Project project) {
|
||||
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
|
||||
sourceSets.all((sourceSet) -> createProhibitedDependenciesChecks(project,
|
||||
sourceSet.getCompileClasspathConfigurationName(), sourceSet.getRuntimeClasspathConfigurationName()));
|
||||
}
|
||||
|
||||
private void createProhibitedDependenciesChecks(Project project, String... configurationNames) {
|
||||
ConfigurationContainer configurations = project.getConfigurations();
|
||||
for (String configurationName : configurationNames) {
|
||||
Configuration configuration = configurations.getByName(configurationName);
|
||||
createProhibitedDependenciesCheck(configuration, project);
|
||||
}
|
||||
}
|
||||
|
||||
private void createProhibitedDependenciesCheck(Configuration classpath, Project project) {
|
||||
String taskName = "check" + StringUtils.capitalize(classpath.getName() + "ForProhibitedDependencies");
|
||||
TaskProvider<CheckClasspathForProhibitedDependencies> checkClasspathTask = project.getTasks().register(taskName,
|
||||
CheckClasspathForProhibitedDependencies.class, (checkClasspath) -> {
|
||||
checkClasspath.setGroup(LifecycleBasePlugin.CHECK_TASK_NAME);
|
||||
checkClasspath.setDescription("Checks " + classpath.getName() + " for prohibited dependencies");
|
||||
checkClasspath.setClasspath(classpath);
|
||||
});
|
||||
project.getTasks().named(SpringCheckProhibitedDependenciesLifecyclePlugin.CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME, (checkProhibitedTask) -> checkProhibitedTask.dependsOn(checkClasspathTask));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.gradle.classpath;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class SpringCheckProhibitedDependenciesLifecyclePlugin implements Plugin<Project> {
|
||||
public static final String CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME = "checkForProhibitedDependencies";
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
TaskProvider<Task> checkProhibitedDependencies = project.getTasks().register(SpringCheckProhibitedDependenciesLifecyclePlugin.CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME, (task) -> {
|
||||
task.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
|
||||
task.setDescription("Checks both the compile/runtime classpath of every SourceSet for prohibited dependencies");
|
||||
});
|
||||
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, (checkTask) -> {
|
||||
checkTask.dependsOn(checkProhibitedDependencies);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2019-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.gradle.docs;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask;
|
||||
import org.asciidoctor.gradle.jvm.AsciidoctorJExtension;
|
||||
import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin;
|
||||
import org.asciidoctor.gradle.jvm.AsciidoctorTask;
|
||||
import org.asciidoctor.gradle.jvm.pdf.AsciidoctorJPdfPlugin;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
|
||||
/**
|
||||
* Conventions that are applied in the presence of the {@link AsciidoctorJPlugin}. When
|
||||
* the plugin is applied:
|
||||
*
|
||||
* <ul>
|
||||
* <li>All warnings are made fatal.
|
||||
* <li>A task is created to resolve and unzip our documentation resources (CSS and
|
||||
* Javascript).
|
||||
* <li>For each {@link AsciidoctorTask} (HTML only):
|
||||
* <ul>
|
||||
* <li>A configuration named asciidoctorExtensions is used to add the
|
||||
* <a href="https://github.com/spring-io/spring-asciidoctor-extensions#block-switch">block
|
||||
* switch</a> extension
|
||||
* <li>{@code doctype} {@link AsciidoctorTask#options(Map) option} is configured.
|
||||
* <li>{@link AsciidoctorTask#attributes(Map) Attributes} are configured for syntax
|
||||
* highlighting, CSS styling, docinfo, etc.
|
||||
* </ul>
|
||||
* <li>For each {@link AbstractAsciidoctorTask} (HTML and PDF):
|
||||
* <ul>
|
||||
* <li>{@link AsciidoctorTask#attributes(Map) Attributes} are configured to enable
|
||||
* warnings for references to missing attributes, the year is added as @{code today-year},
|
||||
* etc
|
||||
* <li>{@link AbstractAsciidoctorTask#baseDirFollowsSourceDir() baseDirFollowsSourceDir()}
|
||||
* is enabled.
|
||||
* </ul>
|
||||
* </ul>
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Rob Winch
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringAsciidoctorPlugin implements Plugin<Project> {
|
||||
private static final String ASCIIDOCTORJ_VERSION = "2.4.3";
|
||||
private static final String EXTENSIONS_CONFIGURATION_NAME = "asciidoctorExtensions";
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
// Apply asciidoctor plugin
|
||||
project.getPluginManager().apply(AsciidoctorJPlugin.class);
|
||||
project.getPluginManager().apply(AsciidoctorJPdfPlugin.class);
|
||||
|
||||
// Configure asciidoctor
|
||||
project.getPlugins().withType(AsciidoctorJPlugin.class, (asciidoctorPlugin) -> {
|
||||
configureDocumentationDependenciesRepository(project);
|
||||
makeAllWarningsFatal(project);
|
||||
upgradeAsciidoctorJVersion(project);
|
||||
createAsciidoctorExtensionsConfiguration(project);
|
||||
project.getTasks().withType(AbstractAsciidoctorTask.class, this::configureAsciidoctorExtension);
|
||||
});
|
||||
}
|
||||
|
||||
private void configureDocumentationDependenciesRepository(Project project) {
|
||||
project.getRepositories().maven((mavenRepo) -> {
|
||||
mavenRepo.setUrl(URI.create("https://repo.spring.io/release"));
|
||||
mavenRepo.mavenContent((mavenContent) -> {
|
||||
mavenContent.includeGroup("io.spring.asciidoctor");
|
||||
mavenContent.includeGroup("io.spring.asciidoctor.backends");
|
||||
mavenContent.includeGroup("io.spring.docresources");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void makeAllWarningsFatal(Project project) {
|
||||
project.getExtensions().getByType(AsciidoctorJExtension.class).fatalWarnings(".*");
|
||||
}
|
||||
|
||||
private void upgradeAsciidoctorJVersion(Project project) {
|
||||
project.getExtensions().getByType(AsciidoctorJExtension.class).setVersion(ASCIIDOCTORJ_VERSION);
|
||||
}
|
||||
|
||||
private void createAsciidoctorExtensionsConfiguration(Project project) {
|
||||
project.getConfigurations().create(EXTENSIONS_CONFIGURATION_NAME, (configuration) -> {
|
||||
project.getConfigurations().matching((candidate) -> "management".equals(candidate.getName()))
|
||||
.all(configuration::extendsFrom);
|
||||
configuration.getDependencies().add(project.getDependencies()
|
||||
.create("io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.3"));
|
||||
configuration.getDependencies()
|
||||
.add(project.getDependencies().create("org.asciidoctor:asciidoctorj-pdf:1.5.3"));
|
||||
});
|
||||
}
|
||||
|
||||
private void configureAsciidoctorExtension(AbstractAsciidoctorTask asciidoctorTask) {
|
||||
asciidoctorTask.configurations(EXTENSIONS_CONFIGURATION_NAME);
|
||||
configureCommonAttributes(asciidoctorTask);
|
||||
configureOptions(asciidoctorTask);
|
||||
asciidoctorTask.baseDirFollowsSourceDir();
|
||||
asciidoctorTask.resources((resourcesSpec) -> {
|
||||
resourcesSpec.from(asciidoctorTask.getSourceDir(), (resourcesSrcDirSpec) -> {
|
||||
// Not using intermediateWorkDir.
|
||||
// See https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/523
|
||||
resourcesSrcDirSpec.include("images/*.png", "css/**", "js/**", "**/*.java");
|
||||
});
|
||||
});
|
||||
if (asciidoctorTask instanceof AsciidoctorTask) {
|
||||
boolean pdf = asciidoctorTask.getName().toLowerCase().contains("pdf");
|
||||
String backend = (!pdf) ? "spring-html" : "spring-pdf";
|
||||
((AsciidoctorTask) asciidoctorTask).outputOptions((outputOptions) ->
|
||||
outputOptions.backends(backend));
|
||||
}
|
||||
}
|
||||
|
||||
private void configureCommonAttributes(AbstractAsciidoctorTask asciidoctorTask) {
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
attributes.put("attribute-missing", "warn");
|
||||
attributes.put("revnumber", null);
|
||||
asciidoctorTask.attributes(attributes);
|
||||
}
|
||||
|
||||
private void configureOptions(AbstractAsciidoctorTask asciidoctorTask) {
|
||||
asciidoctorTask.options(Collections.singletonMap("doctype", "book"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.docs;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.spring.gradle.convention.SpringModulePlugin;
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.javadoc.Javadoc;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringJavadocApiPlugin implements Plugin<Project> {
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
private Set<Pattern> excludes = Collections.singleton(Pattern.compile("test"));
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
// Create task to generate aggregated docs
|
||||
Javadoc api = project.getTasks().create("api", Javadoc.class, (javadoc) -> {
|
||||
javadoc.setGroup("Documentation");
|
||||
javadoc.setDescription("Generates aggregated Javadoc API documentation.");
|
||||
});
|
||||
|
||||
// Note: The following action cannot be a lambda, for groovy compatibility
|
||||
api.doLast(new Action<Task>() {
|
||||
@Override
|
||||
public void execute(Task task) {
|
||||
if (JavaVersion.current().isJava8Compatible()) {
|
||||
project.copy((copy) -> copy.from(api.getDestinationDir())
|
||||
.into(api.getDestinationDir())
|
||||
.include("element-list")
|
||||
.rename("element-list", "package-list"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Set<Project> subprojects = project.getRootProject().getSubprojects();
|
||||
for (Project subproject : subprojects) {
|
||||
addProject(api, subproject);
|
||||
}
|
||||
|
||||
if (subprojects.isEmpty()) {
|
||||
addProject(api, project);
|
||||
}
|
||||
|
||||
api.setMaxMemory("1024m");
|
||||
api.setDestinationDir(new File(project.getBuildDir(), "api"));
|
||||
}
|
||||
|
||||
public void setExcludes(String... excludes) {
|
||||
if (excludes == null) {
|
||||
this.excludes = Collections.emptySet();
|
||||
}
|
||||
this.excludes = new HashSet<>(excludes.length);
|
||||
for (String exclude : excludes) {
|
||||
this.excludes.add(Pattern.compile(exclude));
|
||||
}
|
||||
}
|
||||
|
||||
private void addProject(Javadoc api, Project project) {
|
||||
for (Pattern exclude : excludes) {
|
||||
if (exclude.matcher(project.getName()).matches()) {
|
||||
logger.info("Skipping {} because it is excluded by {}", project, exclude);
|
||||
return;
|
||||
}
|
||||
}
|
||||
logger.info("Try add sources for {}", project);
|
||||
project.getPlugins().withType(SpringModulePlugin.class, (plugin) -> {
|
||||
logger.info("Added sources for {}", project);
|
||||
|
||||
JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class);
|
||||
SourceSet mainSourceSet = java.getSourceSets().getByName("main");
|
||||
|
||||
api.setSource(api.getSource().plus(mainSourceSet.getAllJava()));
|
||||
project.getTasks().withType(Javadoc.class).all((projectJavadoc) ->
|
||||
api.setClasspath(api.getClasspath().plus(projectJavadoc.getClasspath())));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.docs;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.javadoc.Javadoc;
|
||||
import org.gradle.external.javadoc.StandardJavadocDocletOptions;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringJavadocOptionsPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getTasks().withType(Javadoc.class, (javadoc) -> {
|
||||
StandardJavadocDocletOptions options = (StandardJavadocDocletOptions) javadoc.getOptions();
|
||||
options.addStringOption("Xdoclint:none", "-quiet");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.jacoco;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.testing.jacoco.plugins.JacocoPlugin;
|
||||
import org.gradle.testing.jacoco.plugins.JacocoPluginExtension;
|
||||
|
||||
/**
|
||||
* Adds a version of jacoco to use and makes check depend on jacocoTestReport.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringJacocoPlugin implements Plugin<Project> {
|
||||
private static final String JACOCO_TOOL_VERSION_PROPERTY = "jacocoToolVersion";
|
||||
private static final String DEFAULT_JACOCO_TOOL_VERSION = "0.8.7";
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
|
||||
project.getPluginManager().apply(JacocoPlugin.class);
|
||||
project.getTasks().getByName("check").dependsOn(project.getTasks().getByName("jacocoTestReport"));
|
||||
|
||||
JacocoPluginExtension jacoco = project.getExtensions().getByType(JacocoPluginExtension.class);
|
||||
// NOTE: See gradle.properties#jacocoToolVersion for actual version number
|
||||
jacoco.setToolVersion(getJacocoToolVersion(project));
|
||||
});
|
||||
}
|
||||
|
||||
private static String getJacocoToolVersion(Project project) {
|
||||
String jacocoToolVersion = DEFAULT_JACOCO_TOOL_VERSION;
|
||||
if (project.hasProperty(JACOCO_TOOL_VERSION_PROPERTY)) {
|
||||
jacocoToolVersion = Objects.requireNonNull(project.findProperty(JACOCO_TOOL_VERSION_PROPERTY)).toString();
|
||||
}
|
||||
return jacocoToolVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.gradle.management;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaTestFixturesPlugin;
|
||||
import org.gradle.api.plugins.PluginContainer;
|
||||
import org.gradle.api.publish.PublishingExtension;
|
||||
import org.gradle.api.publish.VariantVersionMappingStrategy;
|
||||
import org.gradle.api.publish.maven.MavenPublication;
|
||||
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
|
||||
|
||||
/**
|
||||
* Creates a Management configuration that is appropriate for adding a platform to that is not exposed externally. If
|
||||
* the JavaPlugin is applied, the compileClasspath, runtimeClasspath, testCompileClasspath, and testRuntimeClasspath
|
||||
* will extend from it.
|
||||
* @author Rob Winch
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringManagementConfigurationPlugin implements Plugin<Project> {
|
||||
public static final String MANAGEMENT_CONFIGURATION_NAME = "management";
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
ConfigurationContainer configurations = project.getConfigurations();
|
||||
configurations.create(MANAGEMENT_CONFIGURATION_NAME, (management) -> {
|
||||
management.setVisible(false);
|
||||
management.setCanBeConsumed(false);
|
||||
management.setCanBeResolved(false);
|
||||
|
||||
PluginContainer plugins = project.getPlugins();
|
||||
plugins.withType(JavaPlugin.class, (javaPlugin) -> {
|
||||
configurations.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME).extendsFrom(management);
|
||||
configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME).extendsFrom(management);
|
||||
configurations.getByName(JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME).extendsFrom(management);
|
||||
configurations.getByName(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME).extendsFrom(management);
|
||||
});
|
||||
plugins.withType(JavaTestFixturesPlugin.class, (javaTestFixturesPlugin) -> {
|
||||
configurations.getByName("testFixturesCompileClasspath").extendsFrom(management);
|
||||
configurations.getByName("testFixturesRuntimeClasspath").extendsFrom(management);
|
||||
});
|
||||
plugins.withType(MavenPublishPlugin.class, (mavenPublish) -> {
|
||||
PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
|
||||
publishing.getPublications().withType(MavenPublication.class, (mavenPublication) ->
|
||||
mavenPublication.versionMapping((versions) ->
|
||||
versions.allVariants(VariantVersionMappingStrategy::fromResolutionResult)));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.maven;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.jfrog.gradle.plugin.artifactory.ArtifactoryPlugin;
|
||||
import org.jfrog.gradle.plugin.artifactory.dsl.ArtifactoryPluginConvention;
|
||||
|
||||
import org.springframework.gradle.ProjectUtils;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringArtifactoryPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
// Apply base plugin
|
||||
project.getPlugins().apply(ArtifactoryPlugin.class);
|
||||
|
||||
// Apply artifactory repository configuration
|
||||
boolean isSnapshot = ProjectUtils.isSnapshot(project);
|
||||
boolean isMilestone = ProjectUtils.isMilestone(project);
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
ArtifactoryPluginConvention artifactoryExtension = project.getConvention().getPlugin(ArtifactoryPluginConvention.class);
|
||||
artifactoryExtension.artifactory((artifactory) -> {
|
||||
artifactory.setContextUrl("https://repo.spring.io");
|
||||
artifactory.publish((publish) -> {
|
||||
publish.repository((repository) -> {
|
||||
String repoKey = isSnapshot ? "libs-snapshot-local" : isMilestone ? "libs-milestone-local" : "libs-release-local";
|
||||
repository.setRepoKey(repoKey);
|
||||
if (project.hasProperty("artifactoryUsername")) {
|
||||
repository.setUsername(project.findProperty("artifactoryUsername"));
|
||||
repository.setPassword(project.findProperty("artifactoryPassword"));
|
||||
}
|
||||
});
|
||||
publish.defaults((defaults) -> defaults.publications("mavenJava"));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.maven;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.publish.PublishingExtension;
|
||||
import org.gradle.api.publish.maven.MavenPom;
|
||||
import org.gradle.api.publish.maven.MavenPomDeveloperSpec;
|
||||
import org.gradle.api.publish.maven.MavenPomIssueManagement;
|
||||
import org.gradle.api.publish.maven.MavenPomLicenseSpec;
|
||||
import org.gradle.api.publish.maven.MavenPomOrganization;
|
||||
import org.gradle.api.publish.maven.MavenPomScm;
|
||||
import org.gradle.api.publish.maven.MavenPublication;
|
||||
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringMavenPublishingConventionsPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(MavenPublishPlugin.class, (mavenPublish) -> {
|
||||
PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
|
||||
publishing.getPublications().withType(MavenPublication.class, (mavenPublication) ->
|
||||
customizePom(mavenPublication.getPom(), project));
|
||||
SpringMavenPublishingConventionsPlugin.this.customizeJavaPlugin(project);
|
||||
});
|
||||
}
|
||||
|
||||
private void customizePom(MavenPom pom, Project project) {
|
||||
pom.getUrl().set("https://spring.io/projects/spring-authorization-server");
|
||||
pom.getName().set(project.provider(project::getName));
|
||||
pom.getDescription().set(project.provider(project::getDescription));
|
||||
pom.organization(this::customizeOrganization);
|
||||
pom.licenses(this::customizeLicences);
|
||||
pom.developers(this::customizeDevelopers);
|
||||
pom.scm(this::customizeScm);
|
||||
pom.issueManagement(this::customizeIssueManagement);
|
||||
}
|
||||
|
||||
private void customizeOrganization(MavenPomOrganization organization) {
|
||||
organization.getName().set("VMware, Inc.");
|
||||
organization.getUrl().set("https://spring.io");
|
||||
}
|
||||
|
||||
private void customizeLicences(MavenPomLicenseSpec licences) {
|
||||
licences.license((licence) -> {
|
||||
licence.getName().set("Apache License, Version 2.0");
|
||||
licence.getUrl().set("https://www.apache.org/licenses/LICENSE-2.0");
|
||||
});
|
||||
}
|
||||
|
||||
private void customizeDevelopers(MavenPomDeveloperSpec developers) {
|
||||
developers.developer((developer) -> {
|
||||
developer.getName().set("Joe Grandja");
|
||||
developer.getEmail().set("jgrandja@vmware.com");
|
||||
developer.getOrganization().set("VMware, Inc.");
|
||||
developer.getOrganizationUrl().set("https://spring.io");
|
||||
developer.getRoles().set(Collections.singletonList("Project lead"));
|
||||
});
|
||||
developers.developer((developer) -> {
|
||||
developer.getName().set("Steve Riesenberg");
|
||||
developer.getEmail().set("sriesenberg@vmware.com");
|
||||
developer.getOrganization().set("VMware, Inc.");
|
||||
developer.getOrganizationUrl().set("https://spring.io");
|
||||
});
|
||||
}
|
||||
|
||||
private void customizeScm(MavenPomScm scm) {
|
||||
scm.getConnection().set("scm:git:git://github.com/spring-projects/spring-authorization-server.git");
|
||||
scm.getDeveloperConnection().set("scm:git:ssh://git@github.com/spring-projects/spring-authorization-server.git");
|
||||
scm.getUrl().set("https://github.com/spring-projects/spring-authorization-server");
|
||||
}
|
||||
|
||||
private void customizeIssueManagement(MavenPomIssueManagement issueManagement) {
|
||||
issueManagement.getSystem().set("GitHub");
|
||||
issueManagement.getUrl().set("https://github.com/spring-projects/spring-authorization-server/issues");
|
||||
}
|
||||
|
||||
private void customizeJavaPlugin(Project project) {
|
||||
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
|
||||
JavaPluginExtension extension = project.getExtensions().getByType(JavaPluginExtension.class);
|
||||
extension.withJavadocJar();
|
||||
extension.withSourcesJar();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.maven;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
|
||||
import io.github.gradlenexus.publishplugin.NexusPublishExtension;
|
||||
import io.github.gradlenexus.publishplugin.NexusPublishPlugin;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
|
||||
import org.springframework.gradle.ProjectUtils;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringNexusPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
// Apply nexus publish plugin
|
||||
project.getPlugins().apply(NexusPublishPlugin.class);
|
||||
|
||||
// Create ossrh repository
|
||||
NexusPublishExtension nexusPublishing = project.getExtensions().getByType(NexusPublishExtension.class);
|
||||
nexusPublishing.getRepositories().create("ossrh", (nexusRepository) -> {
|
||||
nexusRepository.getNexusUrl().set(URI.create("https://s01.oss.sonatype.org/service/local/"));
|
||||
nexusRepository.getSnapshotRepositoryUrl().set(URI.create("https://s01.oss.sonatype.org/content/repositories/snapshots/"));
|
||||
});
|
||||
|
||||
// Configure timeouts
|
||||
nexusPublishing.getConnectTimeout().set(Duration.ofMinutes(3));
|
||||
nexusPublishing.getClientTimeout().set(Duration.ofMinutes(3));
|
||||
|
||||
// Ensure release build automatically closes and releases staging repository
|
||||
Task finalizeDeployArtifacts = project.task("finalizeDeployArtifacts");
|
||||
if (ProjectUtils.isRelease(project) && project.hasProperty("ossrhUsername")) {
|
||||
Task closeAndReleaseOssrhStagingRepository = project.getTasks().findByName("closeAndReleaseOssrhStagingRepository");
|
||||
finalizeDeployArtifacts.dependsOn(closeAndReleaseOssrhStagingRepository);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.maven;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaPlatformPlugin;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.publish.PublishingExtension;
|
||||
import org.gradle.api.publish.maven.MavenPublication;
|
||||
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringPublishAllJavaComponentsPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(MavenPublishPlugin.class, (mavenPublish) -> {
|
||||
PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
|
||||
publishing.getPublications().create("mavenJava", MavenPublication.class, (maven) -> {
|
||||
project.getPlugins().withType(JavaPlugin.class, (plugin) ->
|
||||
maven.from(project.getComponents().getByName("java")));
|
||||
project.getPlugins().withType(JavaPlatformPlugin.class, (plugin) ->
|
||||
maven.from(project.getComponents().getByName("javaPlatform")));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.maven;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
|
||||
import org.springframework.gradle.ProjectUtils;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringPublishArtifactsPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getTasks().register("publishArtifacts", (publishArtifacts) -> {
|
||||
publishArtifacts.setGroup("Publishing");
|
||||
publishArtifacts.setDescription("Publish the artifacts to either Artifactory or Maven Central based on the version");
|
||||
if (ProjectUtils.isRelease(project)) {
|
||||
publishArtifacts.dependsOn("publishToOssrh");
|
||||
} else {
|
||||
publishArtifacts.dependsOn("artifactoryPublish");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.maven;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.publish.PublishingExtension;
|
||||
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringPublishLocalPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(MavenPublishPlugin.class, (mavenPublish) -> {
|
||||
PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
|
||||
publishing.getRepositories().maven((maven) -> {
|
||||
maven.setName("local");
|
||||
maven.setUrl(new File(project.getRootProject().getBuildDir(), "publications/repos"));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.maven;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
|
||||
import org.springframework.gradle.ProjectUtils;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringRepositoryPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
List<String> forceMavenRepositories = Collections.emptyList();
|
||||
if (project.hasProperty("forceMavenRepositories")) {
|
||||
forceMavenRepositories = Arrays.asList(((String) project.findProperty("forceMavenRepositories")).split(","));
|
||||
}
|
||||
|
||||
boolean isImplicitSnapshotRepository = forceMavenRepositories.isEmpty() && ProjectUtils.isSnapshot(project);
|
||||
boolean isImplicitMilestoneRepository = forceMavenRepositories.isEmpty() && ProjectUtils.isMilestone(project);
|
||||
|
||||
boolean isSnapshot = isImplicitSnapshotRepository || forceMavenRepositories.contains("snapshot");
|
||||
boolean isMilestone = isImplicitMilestoneRepository || forceMavenRepositories.contains("milestone");
|
||||
|
||||
if (forceMavenRepositories.contains("local")) {
|
||||
project.getRepositories().mavenLocal();
|
||||
}
|
||||
project.getRepositories().mavenCentral();
|
||||
if (isSnapshot) {
|
||||
repository(project, "artifactory-snapshot", "https://repo.spring.io/snapshot/");
|
||||
}
|
||||
if (isSnapshot || isMilestone) {
|
||||
repository(project, "artifactory-milestone", "https://repo.spring.io/milestone/");
|
||||
}
|
||||
repository(project, "artifactory-release", "https://repo.spring.io/release/");
|
||||
}
|
||||
|
||||
private void repository(Project project, String name, String url) {
|
||||
project.getRepositories().maven((repo) -> {
|
||||
repo.setName(name);
|
||||
if (project.hasProperty("artifactoryUsername")) {
|
||||
repo.credentials((credentials) -> {
|
||||
credentials.setUsername(Objects.requireNonNull(project.findProperty("artifactoryUsername")).toString());
|
||||
credentials.setPassword(Objects.requireNonNull(project.findProperty("artifactoryPassword")).toString());
|
||||
});
|
||||
}
|
||||
repo.setUrl(url);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.maven;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.publish.Publication;
|
||||
import org.gradle.api.publish.PublishingExtension;
|
||||
import org.gradle.plugins.signing.SigningExtension;
|
||||
import org.gradle.plugins.signing.SigningPlugin;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringSigningPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPluginManager().apply(SigningPlugin.class);
|
||||
project.getPlugins().withType(SigningPlugin.class, (signingPlugin) -> {
|
||||
boolean hasSigningKey = project.hasProperty("signing.keyId") || project.hasProperty("signingKey");
|
||||
if (hasSigningKey) {
|
||||
sign(project);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void sign(Project project) {
|
||||
SigningExtension signing = project.getExtensions().getByType(SigningExtension.class);
|
||||
signing.setRequired((Callable<Boolean>) () -> project.getGradle().getTaskGraph().hasTask("publishArtifacts"));
|
||||
|
||||
String signingKeyId = (String) project.findProperty("signingKeyId");
|
||||
String signingKey = (String) project.findProperty("signingKey");
|
||||
String signingPassword = (String) project.findProperty("signingPassword");
|
||||
if (signingKeyId != null) {
|
||||
signing.useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword);
|
||||
} else {
|
||||
signing.useInMemoryPgpKeys(signingKey, signingPassword);
|
||||
}
|
||||
project.getPlugins().withType(SpringPublishAllJavaComponentsPlugin.class, (publishingPlugin) -> {
|
||||
PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
|
||||
Publication maven = publishing.getPublications().getByName("mavenJava");
|
||||
signing.sign(maven);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.nohttp;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import io.spring.nohttp.gradle.NoHttpExtension;
|
||||
import io.spring.nohttp.gradle.NoHttpPlugin;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringNoHttpPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
// Apply nohttp plugin
|
||||
project.getPluginManager().apply(NoHttpPlugin.class);
|
||||
|
||||
// Configure nohttp
|
||||
NoHttpExtension nohttp = project.getExtensions().getByType(NoHttpExtension.class);
|
||||
File allowlistFile = project.getRootProject().file("etc/nohttp/allowlist.lines");
|
||||
nohttp.setAllowlistFile(allowlistFile);
|
||||
nohttp.getSource().exclude("buildSrc/build/**");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.propdeps;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.PluginManager;
|
||||
import org.gradle.plugins.ide.eclipse.EclipsePlugin;
|
||||
import org.gradle.plugins.ide.eclipse.EclipseWtpPlugin;
|
||||
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
|
||||
|
||||
/**
|
||||
* Plugin to allow optional and provided dependency configurations to work with the
|
||||
* standard gradle 'eclipse' plugin
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringPropDepsEclipsePlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
PluginManager pluginManager = project.getPluginManager();
|
||||
pluginManager.apply(SpringPropDepsPlugin.class);
|
||||
pluginManager.apply(EclipsePlugin.class);
|
||||
pluginManager.apply(EclipseWtpPlugin.class);
|
||||
|
||||
EclipseModel eclipseModel = project.getExtensions().getByType(EclipseModel.class);
|
||||
eclipseModel.classpath((classpath) -> {
|
||||
classpath.getPlusConfigurations().add(project.getConfigurations().getByName("provided"));
|
||||
classpath.getPlusConfigurations().add(project.getConfigurations().getByName("optional"));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.propdeps;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.PluginManager;
|
||||
import org.gradle.plugins.ide.idea.IdeaPlugin;
|
||||
import org.gradle.plugins.ide.idea.model.IdeaModel;
|
||||
|
||||
/**
|
||||
* Plugin to allow optional and provided dependency configurations to work with the
|
||||
* standard gradle 'idea' plugin
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Brian Clozel
|
||||
* @author Steve Riesenberg
|
||||
* @link https://youtrack.jetbrains.com/issue/IDEA-107046
|
||||
* @link https://youtrack.jetbrains.com/issue/IDEA-117668
|
||||
*/
|
||||
public class SpringPropDepsIdeaPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
PluginManager pluginManager = project.getPluginManager();
|
||||
pluginManager.apply(SpringPropDepsPlugin.class);
|
||||
pluginManager.apply(IdeaPlugin.class);
|
||||
|
||||
IdeaModel ideaModel = project.getExtensions().getByType(IdeaModel.class);
|
||||
ideaModel.module((idea) -> {
|
||||
// IDEA internally deals with 4 scopes : COMPILE, TEST, PROVIDED, RUNTIME
|
||||
// but only PROVIDED seems to be picked up
|
||||
idea.getScopes().get("PROVIDED").get("plus").add(project.getConfigurations().getByName("provided"));
|
||||
idea.getScopes().get("PROVIDED").get("plus").add(project.getConfigurations().getByName("optional"));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.propdeps;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.plugins.JavaLibraryPlugin;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.javadoc.Javadoc;
|
||||
|
||||
import org.springframework.gradle.management.SpringManagementConfigurationPlugin;
|
||||
|
||||
/**
|
||||
* Plugin to allow 'optional' and 'provided' dependency configurations
|
||||
*
|
||||
* As stated in the maven documentation, provided scope "is only available on the compilation and test classpath,
|
||||
* and is not transitive".
|
||||
*
|
||||
* This plugin creates two new configurations, and each one:
|
||||
* <ul>
|
||||
* <li>is a parent of the compile configuration</li>
|
||||
* <li>is not visible, not transitive</li>
|
||||
* <li>all dependencies are excluded from the default configuration</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Brian Clozel
|
||||
* @author Rob Winch
|
||||
* @author Steve Riesenberg
|
||||
*
|
||||
* @see <a href="https://www.gradle.org/docs/current/userguide/java_plugin.html#N121CF">Maven documentation</a>
|
||||
* @see <a href="https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope">Gradle configurations</a>
|
||||
* @see SpringPropDepsEclipsePlugin
|
||||
* @see SpringPropDepsIdeaPlugin
|
||||
*/
|
||||
public class SpringPropDepsPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
|
||||
Configuration provided = addConfiguration(project, "provided");
|
||||
Configuration optional = addConfiguration(project, "optional");
|
||||
|
||||
Javadoc javadoc = (Javadoc) project.getTasks().getByName(JavaPlugin.JAVADOC_TASK_NAME);
|
||||
javadoc.setClasspath(javadoc.getClasspath().plus(provided).plus(optional));
|
||||
});
|
||||
}
|
||||
|
||||
private Configuration addConfiguration(Project project, String name) {
|
||||
Configuration configuration = project.getConfigurations().create(name);
|
||||
configuration.extendsFrom(project.getConfigurations().getByName("implementation"));
|
||||
project.getPlugins().withType(JavaLibraryPlugin.class, (javaLibraryPlugin) ->
|
||||
configuration.extendsFrom(project.getConfigurations().getByName("api")));
|
||||
project.getPlugins().withType(SpringManagementConfigurationPlugin.class, (springManagementConfigurationPlugin) ->
|
||||
configuration.extendsFrom(project.getConfigurations().getByName("management")));
|
||||
|
||||
JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class);
|
||||
java.getSourceSets().all((sourceSet) -> {
|
||||
sourceSet.setCompileClasspath(sourceSet.getCompileClasspath().plus(configuration));
|
||||
sourceSet.setRuntimeClasspath(sourceSet.getRuntimeClasspath().plus(configuration));
|
||||
});
|
||||
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.properties;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringCopyPropertiesPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
copyPropertyFromRootProjectTo("group", project);
|
||||
copyPropertyFromRootProjectTo("version", project);
|
||||
copyPropertyFromRootProjectTo("description", project);
|
||||
}
|
||||
|
||||
private void copyPropertyFromRootProjectTo(String propertyName, Project project) {
|
||||
Project rootProject = project.getRootProject();
|
||||
Object property = rootProject.findProperty(propertyName);
|
||||
if (property != null) {
|
||||
project.setProperty(propertyName, property);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.gradle.sonarqube;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.sonarqube.gradle.SonarQubeExtension;
|
||||
import org.sonarqube.gradle.SonarQubePlugin;
|
||||
|
||||
import org.springframework.gradle.ProjectUtils;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringSonarQubePlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
// Apply sonarqube plugin
|
||||
project.getPluginManager().apply(SonarQubePlugin.class);
|
||||
|
||||
// Configure sonarqube
|
||||
SonarQubeExtension sonarqube = project.getExtensions().getByType(SonarQubeExtension.class);
|
||||
sonarqube.properties((properties) -> {
|
||||
String projectName = ProjectUtils.getProjectName(project);
|
||||
properties.property("sonar.java.coveragePlugin", "jacoco");
|
||||
properties.property("sonar.projectName", projectName);
|
||||
properties.property("sonar.jacoco.reportPath", project.getBuildDir().getName() + "/jacoco.exec");
|
||||
properties.property("sonar.links.homepage", "https://spring.io/" + projectName);
|
||||
properties.property("sonar.links.ci", "https://jenkins.spring.io/job/" + projectName + "/");
|
||||
properties.property("sonar.links.issue", "https://github.com/spring-projects/" + projectName + "/issues");
|
||||
properties.property("sonar.links.scm", "https://github.com/spring-projects/" + projectName);
|
||||
properties.property("sonar.links.scm_dev", "https://github.com/spring-projects/" + projectName + ".git");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
implementation-class=io.spring.gradle.convention.SpringDocsPlugin
|
||||
@@ -0,0 +1 @@
|
||||
implementation-class=io.spring.gradle.convention.SpringRootProjectPlugin
|
||||
@@ -0,0 +1 @@
|
||||
implementation-class=io.spring.gradle.convention.SpringModulePlugin
|
||||
@@ -0,0 +1,2 @@
|
||||
# Referencing this plugin by ID allows java code to depend on groovy compilation
|
||||
implementation-class=org.springframework.gradle.docs.SpringDeployDocsPlugin
|
||||
24
dependencies/spring-authorization-server-dependencies.gradle
vendored
Normal file
24
dependencies/spring-authorization-server-dependencies.gradle
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
plugins {
|
||||
id "java-platform"
|
||||
}
|
||||
|
||||
javaPlatform {
|
||||
allowDependencies()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
|
||||
api platform("org.springframework.security:spring-security-bom:$springSecurityVersion")
|
||||
api platform("com.fasterxml.jackson:jackson-bom:2.13.4.20221013")
|
||||
constraints {
|
||||
api "com.nimbusds:nimbus-jose-jwt:9.24.4"
|
||||
api "javax.servlet:javax.servlet-api:4.0.1"
|
||||
api "junit:junit:4.13.2"
|
||||
api "org.assertj:assertj-core:3.23.1"
|
||||
api "org.mockito:mockito-core:4.8.1"
|
||||
api "com.squareup.okhttp3:mockwebserver:4.10.0"
|
||||
api "com.squareup.okhttp3:okhttp:4.10.0"
|
||||
api "com.jayway.jsonpath:json-path:2.7.0"
|
||||
api "org.hsqldb:hsqldb:2.5.2"
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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 ? 'main' : project.version
|
||||
def ghUrl = "https://github.com/spring-projects/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] }
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url "https://repo.spring.io/release" }
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
@import 'spring.css';
|
||||
|
||||
a code {
|
||||
color: #097dff;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
= 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
|
||||
25
docs/spring-authorization-server-docs.gradle
Normal file
25
docs/spring-authorization-server-docs.gradle
Normal file
@@ -0,0 +1,25 @@
|
||||
plugins {
|
||||
id "io.spring.convention.docs"
|
||||
}
|
||||
|
||||
asciidoctor {
|
||||
attributes([
|
||||
"spring-authorization-server-version": project.version,
|
||||
"spring-security-reference-base-url": "https://docs.spring.io/spring-security/reference",
|
||||
"spring-security-api-base-url": "https://docs.spring.io/spring-security/site/docs/current/api",
|
||||
"examples-dir": "examples",
|
||||
"docs-java": "$sourceDir/examples/src/main/java",
|
||||
"chomp": "default headers packages",
|
||||
"toc": "left",
|
||||
"toclevels": "4"
|
||||
])
|
||||
}
|
||||
|
||||
docsZip {
|
||||
from (api) {
|
||||
into "api"
|
||||
}
|
||||
from(asciidoctor) {
|
||||
into "reference/html"
|
||||
}
|
||||
}
|
||||
255
docs/src/docs/asciidoc/configuration-model.adoc
Normal file
255
docs/src/docs/asciidoc/configuration-model.adoc
Normal file
@@ -0,0 +1,255 @@
|
||||
[[configuration-model]]
|
||||
= Configuration Model
|
||||
|
||||
[[default-configuration]]
|
||||
== Default configuration
|
||||
|
||||
`OAuth2AuthorizationServerConfiguration` is a `@Configuration` that provides the minimal default configuration for an OAuth2 authorization server.
|
||||
|
||||
`OAuth2AuthorizationServerConfiguration` uses <<customizing-the-configuration, `OAuth2AuthorizationServerConfigurer`>> to apply the default configuration and registers a `SecurityFilterChain` `@Bean` composed of all the infrastructure components supporting an OAuth2 authorization server.
|
||||
|
||||
[TIP]
|
||||
`OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(HttpSecurity)` is a convenience (`static`) utility method that applies the default OAuth2 security configuration to `HttpSecurity`.
|
||||
|
||||
The OAuth2 authorization server `SecurityFilterChain` `@Bean` is configured with the following default protocol endpoints:
|
||||
|
||||
* xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[OAuth2 Authorization endpoint]
|
||||
* xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token endpoint]
|
||||
* xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[OAuth2 Token Introspection endpoint]
|
||||
* xref:protocol-endpoints.adoc#oauth2-token-revocation-endpoint[OAuth2 Token Revocation endpoint]
|
||||
* xref:protocol-endpoints.adoc#oauth2-authorization-server-metadata-endpoint[OAuth2 Authorization Server Metadata endpoint]
|
||||
* xref:protocol-endpoints.adoc#jwk-set-endpoint[JWK Set endpoint]
|
||||
|
||||
[NOTE]
|
||||
The JWK Set endpoint is configured *only* if a `JWKSource<SecurityContext>` `@Bean` is registered.
|
||||
|
||||
The following example shows how to use `OAuth2AuthorizationServerConfiguration` to apply the minimal default configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@Import(OAuth2AuthorizationServerConfiguration.class)
|
||||
public class AuthorizationServerConfig {
|
||||
|
||||
@Bean
|
||||
public RegisteredClientRepository registeredClientRepository() {
|
||||
List<RegisteredClient> registrations = ...
|
||||
return new InMemoryRegisteredClientRepository(registrations);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JWKSource<SecurityContext> jwkSource() {
|
||||
RSAKey rsaKey = ...
|
||||
JWKSet jwkSet = new JWKSet(rsaKey);
|
||||
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
[IMPORTANT]
|
||||
The https://datatracker.ietf.org/doc/html/rfc6749#section-4.1[authorization_code grant] requires the resource owner to be authenticated. Therefore, a user authentication mechanism *must* be configured in addition to the default OAuth2 security configuration.
|
||||
|
||||
https://openid.net/specs/openid-connect-core-1_0.html[OpenID Connect 1.0] is disabled in the default configuration. The following example shows how to enable OpenID Connect 1.0 by initializing the `OidcConfigurer`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
|
||||
|
||||
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
|
||||
.oidc(Customizer.withDefaults()); // Initialize `OidcConfigurer`
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
In addition to the default protocol endpoints, the OAuth2 authorization server `SecurityFilterChain` `@Bean` is configured with the following OpenID Connect 1.0 protocol endpoints:
|
||||
|
||||
* xref:protocol-endpoints.adoc#oidc-provider-configuration-endpoint[OpenID Connect 1.0 Provider Configuration endpoint]
|
||||
* xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint]
|
||||
|
||||
[NOTE]
|
||||
The xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint] is disabled by default because many deployments do not require dynamic client registration.
|
||||
|
||||
[TIP]
|
||||
`OAuth2AuthorizationServerConfiguration.jwtDecoder(JWKSource<SecurityContext>)` is a convenience (`static`) utility method that can be used to register a `JwtDecoder` `@Bean`, which is *REQUIRED* for the xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint] and the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint].
|
||||
|
||||
The following example shows how to register a `JwtDecoder` `@Bean`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
|
||||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
||||
}
|
||||
----
|
||||
|
||||
The main intent of `OAuth2AuthorizationServerConfiguration` is to provide a convenient method to apply the minimal default configuration for an OAuth2 authorization server. However, in most cases, customizing the configuration will be required.
|
||||
|
||||
[[customizing-the-configuration]]
|
||||
== Customizing the configuration
|
||||
|
||||
`OAuth2AuthorizationServerConfigurer` provides the ability to fully customize the security configuration for an OAuth2 authorization server.
|
||||
It lets you specify the core components to use - for example, xref:core-model-components.adoc#registered-client-repository[`RegisteredClientRepository`], xref:core-model-components.adoc#oauth2-authorization-service[`OAuth2AuthorizationService`], xref:core-model-components.adoc#oauth2-token-generator[`OAuth2TokenGenerator`], and others.
|
||||
Furthermore, it lets you customize the request processing logic for the protocol endpoints – for example, xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[authorization endpoint], xref:protocol-endpoints.adoc#oauth2-token-endpoint[token endpoint], xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[token introspection endpoint], and others.
|
||||
|
||||
`OAuth2AuthorizationServerConfigurer` provides the following configuration options:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
authorizationServerConfigurer
|
||||
.registeredClientRepository(registeredClientRepository) <1>
|
||||
.authorizationService(authorizationService) <2>
|
||||
.authorizationConsentService(authorizationConsentService) <3>
|
||||
.authorizationServerSettings(authorizationServerSettings) <4>
|
||||
.tokenGenerator(tokenGenerator) <5>
|
||||
.clientAuthentication(clientAuthentication -> { }) <6>
|
||||
.authorizationEndpoint(authorizationEndpoint -> { }) <7>
|
||||
.tokenEndpoint(tokenEndpoint -> { }) <8>
|
||||
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint -> { }) <9>
|
||||
.tokenRevocationEndpoint(tokenRevocationEndpoint -> { }) <10>
|
||||
.authorizationServerMetadataEndpoint(authorizationServerMetadataEndpoint -> { }) <11>
|
||||
.oidc(oidc -> oidc
|
||||
.providerConfigurationEndpoint(providerConfigurationEndpoint -> { }) <12>
|
||||
.userInfoEndpoint(userInfoEndpoint -> { }) <13>
|
||||
.clientRegistrationEndpoint(clientRegistrationEndpoint -> { }) <14>
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
<1> `registeredClientRepository()`: The xref:core-model-components.adoc#registered-client-repository[`RegisteredClientRepository`] (*REQUIRED*) for managing new and existing clients.
|
||||
<2> `authorizationService()`: The xref:core-model-components.adoc#oauth2-authorization-service[`OAuth2AuthorizationService`] for managing new and existing authorizations.
|
||||
<3> `authorizationConsentService()`: The xref:core-model-components.adoc#oauth2-authorization-consent-service[`OAuth2AuthorizationConsentService`] for managing new and existing authorization consents.
|
||||
<4> `authorizationServerSettings()`: The <<configuring-authorization-server-settings, `AuthorizationServerSettings`>> (*REQUIRED*) for customizing configuration settings for the OAuth2 authorization server.
|
||||
<5> `tokenGenerator()`: The xref:core-model-components.adoc#oauth2-token-generator[`OAuth2TokenGenerator`] for generating tokens supported by the OAuth2 authorization server.
|
||||
<6> `clientAuthentication()`: The configurer for <<configuring-client-authentication, OAuth2 Client Authentication>>.
|
||||
<7> `authorizationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[OAuth2 Authorization endpoint].
|
||||
<8> `tokenEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token endpoint].
|
||||
<9> `tokenIntrospectionEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[OAuth2 Token Introspection endpoint].
|
||||
<10> `tokenRevocationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-revocation-endpoint[OAuth2 Token Revocation endpoint].
|
||||
<11> `authorizationServerMetadataEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-authorization-server-metadata-endpoint[OAuth2 Authorization Server Metadata endpoint].
|
||||
<12> `providerConfigurationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-provider-configuration-endpoint[OpenID Connect 1.0 Provider Configuration endpoint].
|
||||
<13> `userInfoEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint].
|
||||
<14> `clientRegistrationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint].
|
||||
|
||||
[[configuring-authorization-server-settings]]
|
||||
== Configuring Authorization Server Settings
|
||||
|
||||
`AuthorizationServerSettings` contains the configuration settings for the OAuth2 authorization server.
|
||||
It specifies the `URI` for the protocol endpoints as well as the https://datatracker.ietf.org/doc/html/rfc8414#section-2[issuer identifier].
|
||||
The default `URI` for the protocol endpoints are as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public final class AuthorizationServerSettings extends AbstractSettings {
|
||||
|
||||
...
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder()
|
||||
.authorizationEndpoint("/oauth2/authorize")
|
||||
.tokenEndpoint("/oauth2/token")
|
||||
.tokenIntrospectionEndpoint("/oauth2/introspect")
|
||||
.tokenRevocationEndpoint("/oauth2/revoke")
|
||||
.jwkSetEndpoint("/oauth2/jwks")
|
||||
.oidcUserInfoEndpoint("/userinfo")
|
||||
.oidcClientRegistrationEndpoint("/connect/register");
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
`AuthorizationServerSettings` is a *REQUIRED* component.
|
||||
|
||||
[TIP]
|
||||
<<default-configuration, `@Import(OAuth2AuthorizationServerConfiguration.class)`>> automatically registers an `AuthorizationServerSettings` `@Bean`, if not already provided.
|
||||
|
||||
The following example shows how to customize the configuration settings and register an `AuthorizationServerSettings` `@Bean`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder()
|
||||
.issuer("https://example.com")
|
||||
.authorizationEndpoint("/oauth2/v1/authorize")
|
||||
.tokenEndpoint("/oauth2/v1/token")
|
||||
.tokenIntrospectionEndpoint("/oauth2/v1/introspect")
|
||||
.tokenRevocationEndpoint("/oauth2/v1/revoke")
|
||||
.jwkSetEndpoint("/oauth2/v1/jwks")
|
||||
.oidcUserInfoEndpoint("/connect/v1/userinfo")
|
||||
.oidcClientRegistrationEndpoint("/connect/v1/register")
|
||||
.build();
|
||||
}
|
||||
----
|
||||
|
||||
The `AuthorizationServerContext` is a context object that holds information of the Authorization Server runtime environment.
|
||||
It provides access to the `AuthorizationServerSettings` and the "`current`" issuer identifier.
|
||||
|
||||
[NOTE]
|
||||
If the issuer identifier is not configured in `AuthorizationServerSettings.builder().issuer(String)`, it is resolved from the current request.
|
||||
|
||||
[NOTE]
|
||||
The `AuthorizationServerContext` is accessible through the `AuthorizationServerContextHolder`, which associates it with the current request thread by using a `ThreadLocal`.
|
||||
|
||||
[[configuring-client-authentication]]
|
||||
== Configuring Client Authentication
|
||||
|
||||
`OAuth2ClientAuthenticationConfigurer` provides the ability to customize https://datatracker.ietf.org/doc/html/rfc6749#section-2.3[OAuth2 client authentication].
|
||||
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for client authentication requests.
|
||||
|
||||
`OAuth2ClientAuthenticationConfigurer` provides the following configuration options:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
authorizationServerConfigurer
|
||||
.clientAuthentication(clientAuthentication ->
|
||||
clientAuthentication
|
||||
.authenticationConverter(authenticationConverter) <1>
|
||||
.authenticationConverters(authenticationConvertersConsumer) <2>
|
||||
.authenticationProvider(authenticationProvider) <3>
|
||||
.authenticationProviders(authenticationProvidersConsumer) <4>
|
||||
.authenticationSuccessHandler(authenticationSuccessHandler) <5>
|
||||
.errorResponseHandler(errorResponseHandler) <6>
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
<1> `authenticationConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract client credentials from `HttpServletRequest` to an instance of `OAuth2ClientAuthenticationToken`.
|
||||
<2> `authenticationConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
|
||||
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2ClientAuthenticationToken`.
|
||||
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
|
||||
<5> `authenticationSuccessHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling a successful client authentication and associating the `OAuth2ClientAuthenticationToken` to the `SecurityContext`.
|
||||
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling a failed client authentication and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.2[`OAuth2Error` response].
|
||||
|
||||
`OAuth2ClientAuthenticationConfigurer` configures the `OAuth2ClientAuthenticationFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
|
||||
`OAuth2ClientAuthenticationFilter` is the `Filter` that processes client authentication requests.
|
||||
|
||||
By default, client authentication is required for the xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token endpoint], the xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[OAuth2 Token Introspection endpoint], and the xref:protocol-endpoints.adoc#oauth2-token-revocation-endpoint[OAuth2 Token Revocation endpoint].
|
||||
The supported client authentication methods are `client_secret_basic`, `client_secret_post`, `private_key_jwt`, `client_secret_jwt`, and `none` (public clients).
|
||||
|
||||
`OAuth2ClientAuthenticationFilter` is configured with the following defaults:
|
||||
|
||||
* `*AuthenticationConverter*` -- A `DelegatingAuthenticationConverter` composed of `JwtClientAssertionAuthenticationConverter`, `ClientSecretBasicAuthenticationConverter`, `ClientSecretPostAuthenticationConverter`, and `PublicClientAuthenticationConverter`.
|
||||
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `JwtClientAssertionAuthenticationProvider`, `ClientSecretAuthenticationProvider`, and `PublicClientAuthenticationProvider`.
|
||||
* `*AuthenticationSuccessHandler*` -- An internal implementation that associates the "`authenticated`" `OAuth2ClientAuthenticationToken` (current `Authentication`) to the `SecurityContext`.
|
||||
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` to return the OAuth2 error response.
|
||||
493
docs/src/docs/asciidoc/core-model-components.adoc
Normal file
493
docs/src/docs/asciidoc/core-model-components.adoc
Normal file
@@ -0,0 +1,493 @@
|
||||
[[core-model-components]]
|
||||
= Core Model / Components
|
||||
|
||||
[[registered-client]]
|
||||
== RegisteredClient
|
||||
|
||||
A `RegisteredClient` is a representation of a client that is https://datatracker.ietf.org/doc/html/rfc6749#section-2[registered] with the authorization server.
|
||||
A client must be registered with the authorization server before it can initiate an authorization grant flow, such as `authorization_code` or `client_credentials`.
|
||||
|
||||
During client registration, the client is assigned a unique https://datatracker.ietf.org/doc/html/rfc6749#section-2.2[client identifier], (optionally) a client secret (depending on https://datatracker.ietf.org/doc/html/rfc6749#section-2.1[client type]), and metadata associated with its unique client identifier.
|
||||
The client's metadata can range from human-facing display strings (such as client name) to items specific to a protocol flow (such as the list of valid redirect URIs).
|
||||
|
||||
[TIP]
|
||||
The corresponding client registration model in Spring Security's OAuth2 Client support is {spring-security-reference-base-url}/servlet/oauth2/client/core.html#oauth2Client-client-registration[ClientRegistration].
|
||||
|
||||
The primary purpose of a client is to request access to protected resources.
|
||||
The client first requests an access token by authenticating with the authorization server and presenting the authorization grant.
|
||||
The authorization server authenticates the client and authorization grant, and, if they are valid, issues an access token.
|
||||
The client can now request the protected resource from the resource server by presenting the access token.
|
||||
|
||||
The following example shows how to configure a `RegisteredClient` that is allowed to perform the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1[authorization_code grant] flow to request an access token:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
|
||||
.clientId("client-a")
|
||||
.clientSecret("{noop}secret") <1>
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.redirectUri("http://127.0.0.1:8080/authorized")
|
||||
.scope("scope-a")
|
||||
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
|
||||
.build();
|
||||
----
|
||||
<1> `\{noop\}` represents the `PasswordEncoder` id for Spring Security's {spring-security-reference-base-url}/features/authentication/password-storage.html#authentication-password-storage-dpe[NoOpPasswordEncoder].
|
||||
|
||||
The corresponding configuration in Spring Security's {spring-security-reference-base-url}/servlet/oauth2/client/index.html[OAuth2 Client support] is:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
client-a:
|
||||
provider: spring
|
||||
client-id: client-a
|
||||
client-secret: secret
|
||||
authorization-grant-type: authorization_code
|
||||
redirect-uri: "http://127.0.0.1:8080/authorized"
|
||||
scope: scope-a
|
||||
provider:
|
||||
spring:
|
||||
issuer-uri: http://localhost:9000
|
||||
----
|
||||
|
||||
A `RegisteredClient` has metadata (attributes) associated with its unique Client Identifier and is defined as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class RegisteredClient implements Serializable {
|
||||
private String id; <1>
|
||||
private String clientId; <2>
|
||||
private Instant clientIdIssuedAt; <3>
|
||||
private String clientSecret; <4>
|
||||
private Instant clientSecretExpiresAt; <5>
|
||||
private String clientName; <6>
|
||||
private Set<ClientAuthenticationMethod> clientAuthenticationMethods; <7>
|
||||
private Set<AuthorizationGrantType> authorizationGrantTypes; <8>
|
||||
private Set<String> redirectUris; <9>
|
||||
private Set<String> scopes; <10>
|
||||
private ClientSettings clientSettings; <11>
|
||||
private TokenSettings tokenSettings; <12>
|
||||
|
||||
...
|
||||
|
||||
}
|
||||
----
|
||||
<1> `id`: The ID that uniquely identifies the `RegisteredClient`.
|
||||
<2> `clientId`: The client identifier.
|
||||
<3> `clientIdIssuedAt`: The time at which the client identifier was issued.
|
||||
<4> `clientSecret`: The client's secret. The value should be encoded using Spring Security's {spring-security-reference-base-url}/features/authentication/password-storage.html#authentication-password-storage-dpe[PasswordEncoder].
|
||||
<5> `clientSecretExpiresAt`: The time at which the client secret expires.
|
||||
<6> `clientName`: A descriptive name used for the client. The name may be used in certain scenarios, such as when displaying the client name in the consent page.
|
||||
<7> `clientAuthenticationMethods`: The authentication method(s) that the client may use. The supported values are `client_secret_basic`, `client_secret_post`, https://datatracker.ietf.org/doc/html/rfc7523[`private_key_jwt`], `client_secret_jwt`, and `none` https://datatracker.ietf.org/doc/html/rfc7636[(public clients)].
|
||||
<8> `authorizationGrantTypes`: The https://datatracker.ietf.org/doc/html/rfc6749#section-1.3[authorization grant type(s)] that the client can use. The supported values are `authorization_code`, `client_credentials`, and `refresh_token`.
|
||||
<9> `redirectUris`: The registered https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2[redirect URI(s)] that the client may use in redirect-based flows – for example, `authorization_code` grant.
|
||||
<10> `scopes`: The scope(s) that the client is allowed to request.
|
||||
<11> `clientSettings`: The custom settings for the client – for example, require https://datatracker.ietf.org/doc/html/rfc7636[PKCE], require authorization consent, and others.
|
||||
<12> `tokenSettings`: The custom settings for the OAuth2 tokens issued to the client – for example, access/refresh token time-to-live, reuse refresh tokens, and others.
|
||||
|
||||
[[registered-client-repository]]
|
||||
== RegisteredClientRepository
|
||||
|
||||
The `RegisteredClientRepository` is the central component where new clients can be registered and existing clients can be queried.
|
||||
It is used by other components when following a specific protocol flow, such as client authentication, authorization grant processing, token introspection, dynamic client registration, and others.
|
||||
|
||||
The provided implementations of `RegisteredClientRepository` are `InMemoryRegisteredClientRepository` and `JdbcRegisteredClientRepository`.
|
||||
The `InMemoryRegisteredClientRepository` implementation stores `RegisteredClient` instances in-memory and is recommended *ONLY* to be used during development and testing.
|
||||
`JdbcRegisteredClientRepository` is a JDBC implementation that persists `RegisteredClient` instances by using `JdbcOperations`.
|
||||
|
||||
[NOTE]
|
||||
The `RegisteredClientRepository` is a *REQUIRED* component.
|
||||
|
||||
The following example shows how to register a `RegisteredClientRepository` `@Bean`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public RegisteredClientRepository registeredClientRepository() {
|
||||
List<RegisteredClient> registrations = ...
|
||||
return new InMemoryRegisteredClientRepository(registrations);
|
||||
}
|
||||
----
|
||||
|
||||
Alternatively, you can configure the `RegisteredClientRepository` through the xref:configuration-model.adoc#customizing-the-configuration[`OAuth2AuthorizationServerConfigurer`]:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
authorizationServerConfigurer
|
||||
.registeredClientRepository(registeredClientRepository);
|
||||
|
||||
...
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
The `OAuth2AuthorizationServerConfigurer` is useful when applying multiple configuration options simultaneously.
|
||||
|
||||
[[oauth2-authorization]]
|
||||
== OAuth2Authorization
|
||||
|
||||
An `OAuth2Authorization` is a representation of an OAuth2 authorization, which holds state related to the authorization granted to a <<registered-client, client>>, by the resource owner or itself in the case of the `client_credentials` authorization grant type.
|
||||
|
||||
[TIP]
|
||||
The corresponding authorization model in Spring Security's OAuth2 Client support is {spring-security-reference-base-url}/servlet/oauth2/client/core.html#oauth2Client-authorized-client[OAuth2AuthorizedClient].
|
||||
|
||||
After the successful completion of an authorization grant flow, an `OAuth2Authorization` is created and associates an {spring-security-api-base-url}/org/springframework/security/oauth2/core/OAuth2AccessToken.html[`OAuth2AccessToken`], an (optional) {spring-security-api-base-url}/org/springframework/security/oauth2/core/OAuth2RefreshToken.html[`OAuth2RefreshToken`], and additional state specific to the executed authorization grant type.
|
||||
|
||||
The {spring-security-api-base-url}/org/springframework/security/oauth2/core/OAuth2Token.html[`OAuth2Token`] instances associated with an `OAuth2Authorization` vary, depending on the authorization grant type.
|
||||
|
||||
For the OAuth2 https://datatracker.ietf.org/doc/html/rfc6749#section-4.1[authorization_code grant], an `OAuth2AuthorizationCode`, an `OAuth2AccessToken`, and an (optional) `OAuth2RefreshToken` are associated.
|
||||
|
||||
For the OpenID Connect 1.0 https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[authorization_code grant], an `OAuth2AuthorizationCode`, an {spring-security-api-base-url}/org/springframework/security/oauth2/core/oidc/OidcIdToken.html[`OidcIdToken`], an `OAuth2AccessToken`, and an (optional) `OAuth2RefreshToken` are associated.
|
||||
|
||||
For the OAuth2 https://datatracker.ietf.org/doc/html/rfc6749#section-4.4[client_credentials grant], only an `OAuth2AccessToken` is associated.
|
||||
|
||||
`OAuth2Authorization` and its attributes are defined as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class OAuth2Authorization implements Serializable {
|
||||
private String id; <1>
|
||||
private String registeredClientId; <2>
|
||||
private String principalName; <3>
|
||||
private AuthorizationGrantType authorizationGrantType; <4>
|
||||
private Set<String> authorizedScopes; <5>
|
||||
private Map<Class<? extends OAuth2Token>, Token<?>> tokens; <6>
|
||||
private Map<String, Object> attributes; <7>
|
||||
|
||||
...
|
||||
|
||||
}
|
||||
----
|
||||
<1> `id`: The ID that uniquely identifies the `OAuth2Authorization`.
|
||||
<2> `registeredClientId`: The ID that uniquely identifies the <<registered-client, RegisteredClient>>.
|
||||
<3> `principalName`: The principal name of the resource owner (or client).
|
||||
<4> `authorizationGrantType`: The `AuthorizationGrantType` used.
|
||||
<5> `authorizedScopes`: The `Set` of scope(s) authorized for the client.
|
||||
<6> `tokens`: The `OAuth2Token` instances (and associated metadata) specific to the executed authorization grant type.
|
||||
<7> `attributes`: The additional attributes specific to the executed authorization grant type – for example, the authenticated `Principal`, `OAuth2AuthorizationRequest`, and others.
|
||||
|
||||
`OAuth2Authorization` and its associated `OAuth2Token` instances have a set lifespan.
|
||||
A newly issued `OAuth2Token` is active and becomes inactive when it either expires or is invalidated (revoked).
|
||||
The `OAuth2Authorization` is (implicitly) inactive when all associated `OAuth2Token` instances are inactive.
|
||||
Each `OAuth2Token` is held in an `OAuth2Authorization.Token`, which provides accessors for `isExpired()`, `isInvalidated()`, and `isActive()`.
|
||||
|
||||
`OAuth2Authorization.Token` also provides `getClaims()`, which returns the claims (if any) associated with the `OAuth2Token`.
|
||||
|
||||
[[oauth2-authorization-service]]
|
||||
== OAuth2AuthorizationService
|
||||
|
||||
The `OAuth2AuthorizationService` is the central component where new authorizations are stored and existing authorizations are queried.
|
||||
It is used by other components when following a specific protocol flow – for example, client authentication, authorization grant processing, token introspection, token revocation, dynamic client registration, and others.
|
||||
|
||||
The provided implementations of `OAuth2AuthorizationService` are `InMemoryOAuth2AuthorizationService` and `JdbcOAuth2AuthorizationService`.
|
||||
The `InMemoryOAuth2AuthorizationService` implementation stores `OAuth2Authorization` instances in-memory and is recommended *ONLY* to be used during development and testing.
|
||||
`JdbcOAuth2AuthorizationService` is a JDBC implementation that persists `OAuth2Authorization` instances by using `JdbcOperations`.
|
||||
|
||||
[NOTE]
|
||||
The `OAuth2AuthorizationService` is an *OPTIONAL* component and defaults to `InMemoryOAuth2AuthorizationService`.
|
||||
|
||||
The following example shows how to register an `OAuth2AuthorizationService` `@Bean`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public OAuth2AuthorizationService authorizationService() {
|
||||
return new InMemoryOAuth2AuthorizationService();
|
||||
}
|
||||
----
|
||||
|
||||
Alternatively, you can configure the `OAuth2AuthorizationService` through the xref:configuration-model.adoc#customizing-the-configuration[`OAuth2AuthorizationServerConfigurer`]:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
authorizationServerConfigurer
|
||||
.authorizationService(authorizationService);
|
||||
|
||||
...
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
The `OAuth2AuthorizationServerConfigurer` is useful when applying multiple configuration options simultaneously.
|
||||
|
||||
[[oauth2-authorization-consent]]
|
||||
== OAuth2AuthorizationConsent
|
||||
|
||||
An `OAuth2AuthorizationConsent` is a representation of an authorization "consent" (decision) from an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1[OAuth2 authorization request flow] – for example, the `authorization_code` grant, which holds the authorities granted to a <<registered-client, client>> by the resource owner.
|
||||
|
||||
When authorizing access to a client, the resource owner may grant only a subset of the authorities requested by the client.
|
||||
The typical use case is the `authorization_code` grant flow, in which the client requests scope(s) and the resource owner grants (or denies) access to the requested scope(s).
|
||||
|
||||
After the completion of an OAuth2 authorization request flow, an `OAuth2AuthorizationConsent` is created (or updated) and associates the granted authorities with the client and resource owner.
|
||||
|
||||
`OAuth2AuthorizationConsent` and its attributes are defined as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public final class OAuth2AuthorizationConsent implements Serializable {
|
||||
private final String registeredClientId; <1>
|
||||
private final String principalName; <2>
|
||||
private final Set<GrantedAuthority> authorities; <3>
|
||||
|
||||
...
|
||||
|
||||
}
|
||||
----
|
||||
<1> `registeredClientId`: The ID that uniquely identifies the <<registered-client, RegisteredClient>>.
|
||||
<2> `principalName`: The principal name of the resource owner.
|
||||
<3> `authorities`: The authorities granted to the client by the resource owner. An authority can represent a scope, a claim, a permission, a role, and others.
|
||||
|
||||
[[oauth2-authorization-consent-service]]
|
||||
== OAuth2AuthorizationConsentService
|
||||
|
||||
The `OAuth2AuthorizationConsentService` is the central component where new authorization consents are stored and existing authorization consents are queried.
|
||||
It is primarily used by components that implement an OAuth2 authorization request flow – for example, the `authorization_code` grant.
|
||||
|
||||
The provided implementations of `OAuth2AuthorizationConsentService` are `InMemoryOAuth2AuthorizationConsentService` and `JdbcOAuth2AuthorizationConsentService`.
|
||||
The `InMemoryOAuth2AuthorizationConsentService` implementation stores `OAuth2AuthorizationConsent` instances in-memory and is recommended *ONLY* for development and testing.
|
||||
`JdbcOAuth2AuthorizationConsentService` is a JDBC implementation that persists `OAuth2AuthorizationConsent` instances by using `JdbcOperations`.
|
||||
|
||||
[NOTE]
|
||||
The `OAuth2AuthorizationConsentService` is an *OPTIONAL* component and defaults to `InMemoryOAuth2AuthorizationConsentService`.
|
||||
|
||||
The following example shows how to register an `OAuth2AuthorizationConsentService` `@Bean`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public OAuth2AuthorizationConsentService authorizationConsentService() {
|
||||
return new InMemoryOAuth2AuthorizationConsentService();
|
||||
}
|
||||
----
|
||||
|
||||
Alternatively, you can configure the `OAuth2AuthorizationConsentService` through the xref:configuration-model.adoc#customizing-the-configuration[`OAuth2AuthorizationServerConfigurer`]:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
authorizationServerConfigurer
|
||||
.authorizationConsentService(authorizationConsentService);
|
||||
|
||||
...
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
The `OAuth2AuthorizationServerConfigurer` is useful when applying multiple configuration options simultaneously.
|
||||
|
||||
[[oauth2-token-context]]
|
||||
== OAuth2TokenContext
|
||||
|
||||
An `OAuth2TokenContext` is a context object that holds information associated with an `OAuth2Token` and is used by an <<oauth2-token-generator, OAuth2TokenGenerator>> and <<oauth2-token-customizer, OAuth2TokenCustomizer>>.
|
||||
|
||||
`OAuth2TokenContext` provides the following accessors:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public interface OAuth2TokenContext extends Context {
|
||||
|
||||
default RegisteredClient getRegisteredClient() ... <1>
|
||||
|
||||
default <T extends Authentication> T getPrincipal() ... <2>
|
||||
|
||||
default AuthorizationServerContext getAuthorizationServerContext() ... <3>
|
||||
|
||||
@Nullable
|
||||
default OAuth2Authorization getAuthorization() ... <4>
|
||||
|
||||
default Set<String> getAuthorizedScopes() ... <5>
|
||||
|
||||
default OAuth2TokenType getTokenType() ... <6>
|
||||
|
||||
default AuthorizationGrantType getAuthorizationGrantType() ... <7>
|
||||
|
||||
default <T extends Authentication> T getAuthorizationGrant() ... <8>
|
||||
|
||||
...
|
||||
|
||||
}
|
||||
----
|
||||
<1> `getRegisteredClient()`: The <<registered-client, RegisteredClient>> associated with the authorization grant.
|
||||
<2> `getPrincipal()`: The `Authentication` instance of the resource owner (or client).
|
||||
<3> `getAuthorizationServerContext()`: The xref:configuration-model.adoc#configuring-authorization-server-settings[`AuthorizationServerContext`] object that holds information of the Authorization Server runtime environment.
|
||||
<4> `getAuthorization()`: The <<oauth2-authorization, OAuth2Authorization>> associated with the authorization grant.
|
||||
<5> `getAuthorizedScopes()`: The scope(s) authorized for the client.
|
||||
<6> `getTokenType()`: The `OAuth2TokenType` to generate. The supported values are `code`, `access_token`, `refresh_token`, and `id_token`.
|
||||
<7> `getAuthorizationGrantType()`: The `AuthorizationGrantType` associated with the authorization grant.
|
||||
<8> `getAuthorizationGrant()`: The `Authentication` instance used by the `AuthenticationProvider` that processes the authorization grant.
|
||||
|
||||
[[oauth2-token-generator]]
|
||||
== OAuth2TokenGenerator
|
||||
|
||||
An `OAuth2TokenGenerator` is responsible for generating an `OAuth2Token` from the information contained in the provided <<oauth2-token-context, OAuth2TokenContext>>.
|
||||
|
||||
The `OAuth2Token` generated primarily depends on the type of `OAuth2TokenType` specified in the `OAuth2TokenContext`.
|
||||
|
||||
For example, when the `value` for `OAuth2TokenType` is:
|
||||
|
||||
* `code`, then `OAuth2AuthorizationCode` is generated.
|
||||
* `access_token`, then `OAuth2AccessToken` is generated.
|
||||
* `refresh_token`, then `OAuth2RefreshToken` is generated.
|
||||
* `id_token`, then `OidcIdToken` is generated.
|
||||
|
||||
Furthermore, the format of the generated `OAuth2AccessToken` varies, depending on the `TokenSettings.getAccessTokenFormat()` configured for the <<registered-client, RegisteredClient>>.
|
||||
If the format is `OAuth2TokenFormat.SELF_CONTAINED` (the default), then a `Jwt` is generated.
|
||||
If the format is `OAuth2TokenFormat.REFERENCE`, then an "opaque" token is generated.
|
||||
|
||||
Finally, if the generated `OAuth2Token` has a set of claims and implements `ClaimAccessor`, the claims are made accessible from <<oauth2-authorization, OAuth2Authorization.Token.getClaims()>>.
|
||||
|
||||
The `OAuth2TokenGenerator` is primarily used by components that implement authorization grant processing – for example, `authorization_code`, `client_credentials`, and `refresh_token`.
|
||||
|
||||
The provided implementations are `OAuth2AccessTokenGenerator`, `OAuth2RefreshTokenGenerator`, and `JwtGenerator`.
|
||||
The `OAuth2AccessTokenGenerator` generates an "opaque" (`OAuth2TokenFormat.REFERENCE`) access token, and the `JwtGenerator` generates a `Jwt` (`OAuth2TokenFormat.SELF_CONTAINED`).
|
||||
|
||||
[NOTE]
|
||||
The `OAuth2TokenGenerator` is an *OPTIONAL* component and defaults to a `DelegatingOAuth2TokenGenerator` composed of an `OAuth2AccessTokenGenerator` and `OAuth2RefreshTokenGenerator`.
|
||||
|
||||
[NOTE]
|
||||
If a `JwtEncoder` `@Bean` or `JWKSource<SecurityContext>` `@Bean` is registered, then a `JwtGenerator` is additionally composed in the `DelegatingOAuth2TokenGenerator`.
|
||||
|
||||
The `OAuth2TokenGenerator` provides great flexibility, as it can support any custom token format for `access_token` and `refresh_token`.
|
||||
|
||||
The following example shows how to register an `OAuth2TokenGenerator` `@Bean`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public OAuth2TokenGenerator<?> tokenGenerator() {
|
||||
JwtEncoder jwtEncoder = ...
|
||||
JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
|
||||
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
||||
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
|
||||
return new DelegatingOAuth2TokenGenerator(
|
||||
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
|
||||
}
|
||||
----
|
||||
|
||||
Alternatively, you can configure the `OAuth2TokenGenerator` through the xref:configuration-model.adoc#customizing-the-configuration[`OAuth2AuthorizationServerConfigurer`]:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
authorizationServerConfigurer
|
||||
.tokenGenerator(tokenGenerator);
|
||||
|
||||
...
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
The `OAuth2AuthorizationServerConfigurer` is useful when applying multiple configuration options simultaneously.
|
||||
|
||||
[[oauth2-token-customizer]]
|
||||
== OAuth2TokenCustomizer
|
||||
|
||||
An `OAuth2TokenCustomizer` provides the ability to customize the attributes of an `OAuth2Token`, which are accessible in the provided <<oauth2-token-context, OAuth2TokenContext>>.
|
||||
It is used by an <<oauth2-token-generator, OAuth2TokenGenerator>> to let it customize the attributes of the `OAuth2Token` before it is generated.
|
||||
|
||||
An `OAuth2TokenCustomizer<OAuth2TokenClaimsContext>` declared with a generic type of `OAuth2TokenClaimsContext` (`implements OAuth2TokenContext`) provides the ability to customize the claims of an "opaque" `OAuth2AccessToken`.
|
||||
`OAuth2TokenClaimsContext.getClaims()` provides access to the `OAuth2TokenClaimsSet.Builder`, allowing the ability to add, replace, and remove claims.
|
||||
|
||||
The following example shows how to implement an `OAuth2TokenCustomizer<OAuth2TokenClaimsContext>` and configure it with an `OAuth2AccessTokenGenerator`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public OAuth2TokenGenerator<?> tokenGenerator() {
|
||||
JwtEncoder jwtEncoder = ...
|
||||
JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
|
||||
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
||||
accessTokenGenerator.setAccessTokenCustomizer(accessTokenCustomizer());
|
||||
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
|
||||
return new DelegatingOAuth2TokenGenerator(
|
||||
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer() {
|
||||
return context -> {
|
||||
OAuth2TokenClaimsSet.Builder claims = context.getClaims();
|
||||
// Customize claims
|
||||
|
||||
};
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
If the `OAuth2TokenGenerator` is not provided as a `@Bean` or is not configured through the `OAuth2AuthorizationServerConfigurer`, an `OAuth2TokenCustomizer<OAuth2TokenClaimsContext>` `@Bean` will automatically be configured with an `OAuth2AccessTokenGenerator`.
|
||||
|
||||
An `OAuth2TokenCustomizer<JwtEncodingContext>` declared with a generic type of `JwtEncodingContext` (`implements OAuth2TokenContext`) provides the ability to customize the headers and claims of a `Jwt`.
|
||||
`JwtEncodingContext.getHeaders()` provides access to the `JwsHeader.Builder`, allowing the ability to add, replace, and remove headers.
|
||||
`JwtEncodingContext.getClaims()` provides access to the `JwtClaimsSet.Builder`, allowing the ability to add, replace, and remove claims.
|
||||
|
||||
The following example shows how to implement an `OAuth2TokenCustomizer<JwtEncodingContext>` and configure it with a `JwtGenerator`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public OAuth2TokenGenerator<?> tokenGenerator() {
|
||||
JwtEncoder jwtEncoder = ...
|
||||
JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
|
||||
jwtGenerator.setJwtCustomizer(jwtCustomizer());
|
||||
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
||||
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
|
||||
return new DelegatingOAuth2TokenGenerator(
|
||||
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
|
||||
return context -> {
|
||||
JwsHeader.Builder headers = context.getHeaders();
|
||||
JwtClaimsSet.Builder claims = context.getClaims();
|
||||
if (context.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) {
|
||||
// Customize headers/claims for access_token
|
||||
|
||||
} else if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) {
|
||||
// Customize headers/claims for id_token
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
If the `OAuth2TokenGenerator` is not provided as a `@Bean` or is not configured through the `OAuth2AuthorizationServerConfigurer`, an `OAuth2TokenCustomizer<JwtEncodingContext>` `@Bean` will automatically be configured with a `JwtGenerator`.
|
||||
|
||||
[TIP]
|
||||
For an example showing how you can xref:guides/how-to-userinfo.adoc#customize-id-token[customize the ID token], see the guide xref:guides/how-to-userinfo.adoc#how-to-userinfo[How-to: Customize the OpenID Connect 1.0 UserInfo response].
|
||||
@@ -0,0 +1,30 @@
|
||||
plugins {
|
||||
id "java"
|
||||
}
|
||||
|
||||
group = project.rootProject.group
|
||||
version = project.rootProject.version
|
||||
sourceCompatibility = "1.8"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation platform("org.springframework.boot:spring-boot-dependencies:2.7.5")
|
||||
implementation "org.springframework.boot:spring-boot-starter-web"
|
||||
implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
|
||||
implementation "org.springframework.boot:spring-boot-starter-security"
|
||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
|
||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-resource-server"
|
||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
|
||||
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5"
|
||||
implementation project(":spring-security-oauth2-authorization-server")
|
||||
runtimeOnly "com.h2database:h2"
|
||||
testImplementation "org.springframework.boot:spring-boot-starter-test"
|
||||
testImplementation "org.springframework.security:spring-security-test"
|
||||
}
|
||||
|
||||
tasks.named("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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 sample.gettingStarted;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
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.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
||||
|
||||
@Configuration
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean // <1>
|
||||
@Order(1)
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
|
||||
throws Exception {
|
||||
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
|
||||
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
|
||||
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
|
||||
// @formatter:off
|
||||
http
|
||||
// Redirect to the login page when not authenticated from the
|
||||
// authorization endpoint
|
||||
.exceptionHandling((exceptions) -> exceptions
|
||||
.authenticationEntryPoint(
|
||||
new LoginUrlAuthenticationEntryPoint("/login"))
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean // <2>
|
||||
@Order(2)
|
||||
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
|
||||
throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
// Form login handles the redirect to the login page from the
|
||||
// authorization server filter chain
|
||||
.formLogin(Customizer.withDefaults());
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean // <3>
|
||||
public UserDetailsService userDetailsService() {
|
||||
// @formatter:off
|
||||
UserDetails userDetails = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
return new InMemoryUserDetailsManager(userDetails);
|
||||
}
|
||||
|
||||
@Bean // <4>
|
||||
public RegisteredClientRepository registeredClientRepository() {
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
|
||||
.clientId("messaging-client")
|
||||
.clientSecret("{noop}secret")
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
|
||||
.redirectUri("http://127.0.0.1:8080/authorized")
|
||||
.scope(OidcScopes.OPENID)
|
||||
.scope("message.read")
|
||||
.scope("message.write")
|
||||
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
return new InMemoryRegisteredClientRepository(registeredClient);
|
||||
}
|
||||
|
||||
@Bean // <5>
|
||||
public JWKSource<SecurityContext> jwkSource() {
|
||||
KeyPair keyPair = generateRsaKey();
|
||||
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
|
||||
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
|
||||
// @formatter:off
|
||||
RSAKey rsaKey = new RSAKey.Builder(publicKey)
|
||||
.privateKey(privateKey)
|
||||
.keyID(UUID.randomUUID().toString())
|
||||
.build();
|
||||
// @formatter:on
|
||||
JWKSet jwkSet = new JWKSet(rsaKey);
|
||||
return new ImmutableJWKSet<>(jwkSet);
|
||||
}
|
||||
|
||||
private static KeyPair generateRsaKey() { // <6>
|
||||
KeyPair keyPair;
|
||||
try {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(2048);
|
||||
keyPair = keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
@Bean // <7>
|
||||
public AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder().build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright 2022 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 sample.jpa.entity.authorization;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "`authorization`")
|
||||
public class Authorization {
|
||||
@Id
|
||||
@Column
|
||||
private String id;
|
||||
private String registeredClientId;
|
||||
private String principalName;
|
||||
private String authorizationGrantType;
|
||||
@Column(length = 1000)
|
||||
private String authorizedScopes;
|
||||
@Column(length = 4000)
|
||||
private String attributes;
|
||||
@Column(length = 500)
|
||||
private String state;
|
||||
|
||||
@Column(length = 4000)
|
||||
private String authorizationCodeValue;
|
||||
private Instant authorizationCodeIssuedAt;
|
||||
private Instant authorizationCodeExpiresAt;
|
||||
private String authorizationCodeMetadata;
|
||||
|
||||
@Column(length = 4000)
|
||||
private String accessTokenValue;
|
||||
private Instant accessTokenIssuedAt;
|
||||
private Instant accessTokenExpiresAt;
|
||||
@Column(length = 2000)
|
||||
private String accessTokenMetadata;
|
||||
private String accessTokenType;
|
||||
@Column(length = 1000)
|
||||
private String accessTokenScopes;
|
||||
|
||||
@Column(length = 4000)
|
||||
private String refreshTokenValue;
|
||||
private Instant refreshTokenIssuedAt;
|
||||
private Instant refreshTokenExpiresAt;
|
||||
@Column(length = 2000)
|
||||
private String refreshTokenMetadata;
|
||||
|
||||
@Column(length = 4000)
|
||||
private String oidcIdTokenValue;
|
||||
private Instant oidcIdTokenIssuedAt;
|
||||
private Instant oidcIdTokenExpiresAt;
|
||||
@Column(length = 2000)
|
||||
private String oidcIdTokenMetadata;
|
||||
@Column(length = 2000)
|
||||
private String oidcIdTokenClaims;
|
||||
|
||||
// @fold:on
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getRegisteredClientId() {
|
||||
return registeredClientId;
|
||||
}
|
||||
|
||||
public void setRegisteredClientId(String registeredClientId) {
|
||||
this.registeredClientId = registeredClientId;
|
||||
}
|
||||
|
||||
public String getPrincipalName() {
|
||||
return principalName;
|
||||
}
|
||||
|
||||
public void setPrincipalName(String principalName) {
|
||||
this.principalName = principalName;
|
||||
}
|
||||
|
||||
public String getAuthorizationGrantType() {
|
||||
return authorizationGrantType;
|
||||
}
|
||||
|
||||
public void setAuthorizationGrantType(String authorizationGrantType) {
|
||||
this.authorizationGrantType = authorizationGrantType;
|
||||
}
|
||||
|
||||
public String getAuthorizedScopes() {
|
||||
return this.authorizedScopes;
|
||||
}
|
||||
|
||||
public void setAuthorizedScopes(String authorizedScopes) {
|
||||
this.authorizedScopes = authorizedScopes;
|
||||
}
|
||||
|
||||
public String getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(String attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public String getAuthorizationCodeValue() {
|
||||
return authorizationCodeValue;
|
||||
}
|
||||
|
||||
public void setAuthorizationCodeValue(String authorizationCode) {
|
||||
this.authorizationCodeValue = authorizationCode;
|
||||
}
|
||||
|
||||
public Instant getAuthorizationCodeIssuedAt() {
|
||||
return authorizationCodeIssuedAt;
|
||||
}
|
||||
|
||||
public void setAuthorizationCodeIssuedAt(Instant authorizationCodeIssuedAt) {
|
||||
this.authorizationCodeIssuedAt = authorizationCodeIssuedAt;
|
||||
}
|
||||
|
||||
public Instant getAuthorizationCodeExpiresAt() {
|
||||
return authorizationCodeExpiresAt;
|
||||
}
|
||||
|
||||
public void setAuthorizationCodeExpiresAt(Instant authorizationCodeExpiresAt) {
|
||||
this.authorizationCodeExpiresAt = authorizationCodeExpiresAt;
|
||||
}
|
||||
|
||||
public String getAuthorizationCodeMetadata() {
|
||||
return authorizationCodeMetadata;
|
||||
}
|
||||
|
||||
public void setAuthorizationCodeMetadata(String authorizationCodeMetadata) {
|
||||
this.authorizationCodeMetadata = authorizationCodeMetadata;
|
||||
}
|
||||
|
||||
public String getAccessTokenValue() {
|
||||
return accessTokenValue;
|
||||
}
|
||||
|
||||
public void setAccessTokenValue(String accessToken) {
|
||||
this.accessTokenValue = accessToken;
|
||||
}
|
||||
|
||||
public Instant getAccessTokenIssuedAt() {
|
||||
return accessTokenIssuedAt;
|
||||
}
|
||||
|
||||
public void setAccessTokenIssuedAt(Instant accessTokenIssuedAt) {
|
||||
this.accessTokenIssuedAt = accessTokenIssuedAt;
|
||||
}
|
||||
|
||||
public Instant getAccessTokenExpiresAt() {
|
||||
return accessTokenExpiresAt;
|
||||
}
|
||||
|
||||
public void setAccessTokenExpiresAt(Instant accessTokenExpiresAt) {
|
||||
this.accessTokenExpiresAt = accessTokenExpiresAt;
|
||||
}
|
||||
|
||||
public String getAccessTokenMetadata() {
|
||||
return accessTokenMetadata;
|
||||
}
|
||||
|
||||
public void setAccessTokenMetadata(String accessTokenMetadata) {
|
||||
this.accessTokenMetadata = accessTokenMetadata;
|
||||
}
|
||||
|
||||
public String getAccessTokenType() {
|
||||
return accessTokenType;
|
||||
}
|
||||
|
||||
public void setAccessTokenType(String accessTokenType) {
|
||||
this.accessTokenType = accessTokenType;
|
||||
}
|
||||
|
||||
public String getAccessTokenScopes() {
|
||||
return accessTokenScopes;
|
||||
}
|
||||
|
||||
public void setAccessTokenScopes(String accessTokenScopes) {
|
||||
this.accessTokenScopes = accessTokenScopes;
|
||||
}
|
||||
|
||||
public String getRefreshTokenValue() {
|
||||
return refreshTokenValue;
|
||||
}
|
||||
|
||||
public void setRefreshTokenValue(String refreshToken) {
|
||||
this.refreshTokenValue = refreshToken;
|
||||
}
|
||||
|
||||
public Instant getRefreshTokenIssuedAt() {
|
||||
return refreshTokenIssuedAt;
|
||||
}
|
||||
|
||||
public void setRefreshTokenIssuedAt(Instant refreshTokenIssuedAt) {
|
||||
this.refreshTokenIssuedAt = refreshTokenIssuedAt;
|
||||
}
|
||||
|
||||
public Instant getRefreshTokenExpiresAt() {
|
||||
return refreshTokenExpiresAt;
|
||||
}
|
||||
|
||||
public void setRefreshTokenExpiresAt(Instant refreshTokenExpiresAt) {
|
||||
this.refreshTokenExpiresAt = refreshTokenExpiresAt;
|
||||
}
|
||||
|
||||
public String getRefreshTokenMetadata() {
|
||||
return refreshTokenMetadata;
|
||||
}
|
||||
|
||||
public void setRefreshTokenMetadata(String refreshTokenMetadata) {
|
||||
this.refreshTokenMetadata = refreshTokenMetadata;
|
||||
}
|
||||
|
||||
public String getOidcIdTokenValue() {
|
||||
return oidcIdTokenValue;
|
||||
}
|
||||
|
||||
public void setOidcIdTokenValue(String idToken) {
|
||||
this.oidcIdTokenValue = idToken;
|
||||
}
|
||||
|
||||
public Instant getOidcIdTokenIssuedAt() {
|
||||
return oidcIdTokenIssuedAt;
|
||||
}
|
||||
|
||||
public void setOidcIdTokenIssuedAt(Instant idTokenIssuedAt) {
|
||||
this.oidcIdTokenIssuedAt = idTokenIssuedAt;
|
||||
}
|
||||
|
||||
public Instant getOidcIdTokenExpiresAt() {
|
||||
return oidcIdTokenExpiresAt;
|
||||
}
|
||||
|
||||
public void setOidcIdTokenExpiresAt(Instant idTokenExpiresAt) {
|
||||
this.oidcIdTokenExpiresAt = idTokenExpiresAt;
|
||||
}
|
||||
|
||||
public String getOidcIdTokenMetadata() {
|
||||
return oidcIdTokenMetadata;
|
||||
}
|
||||
|
||||
public void setOidcIdTokenMetadata(String idTokenMetadata) {
|
||||
this.oidcIdTokenMetadata = idTokenMetadata;
|
||||
}
|
||||
|
||||
public String getOidcIdTokenClaims() {
|
||||
return oidcIdTokenClaims;
|
||||
}
|
||||
|
||||
public void setOidcIdTokenClaims(String idTokenClaims) {
|
||||
this.oidcIdTokenClaims = idTokenClaims;
|
||||
}
|
||||
// @fold:off
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2022 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 sample.jpa.entity.authorizationConsent;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "`authorizationConsent`")
|
||||
@IdClass(AuthorizationConsent.AuthorizationConsentId.class)
|
||||
public class AuthorizationConsent {
|
||||
@Id
|
||||
private String registeredClientId;
|
||||
@Id
|
||||
private String principalName;
|
||||
@Column(length = 1000)
|
||||
private String authorities;
|
||||
|
||||
// @fold:on
|
||||
public String getRegisteredClientId() {
|
||||
return registeredClientId;
|
||||
}
|
||||
|
||||
public void setRegisteredClientId(String registeredClientId) {
|
||||
this.registeredClientId = registeredClientId;
|
||||
}
|
||||
|
||||
public String getPrincipalName() {
|
||||
return principalName;
|
||||
}
|
||||
|
||||
public void setPrincipalName(String principalName) {
|
||||
this.principalName = principalName;
|
||||
}
|
||||
|
||||
public String getAuthorities() {
|
||||
return authorities;
|
||||
}
|
||||
|
||||
public void setAuthorities(String authorities) {
|
||||
this.authorities = authorities;
|
||||
}
|
||||
// @fold:off
|
||||
|
||||
public static class AuthorizationConsentId implements Serializable {
|
||||
private String registeredClientId;
|
||||
private String principalName;
|
||||
|
||||
// @fold:on
|
||||
public String getRegisteredClientId() {
|
||||
return registeredClientId;
|
||||
}
|
||||
|
||||
public void setRegisteredClientId(String registeredClientId) {
|
||||
this.registeredClientId = registeredClientId;
|
||||
}
|
||||
|
||||
public String getPrincipalName() {
|
||||
return principalName;
|
||||
}
|
||||
|
||||
public void setPrincipalName(String principalName) {
|
||||
this.principalName = principalName;
|
||||
}
|
||||
// @fold:off
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
AuthorizationConsentId that = (AuthorizationConsentId) o;
|
||||
return registeredClientId.equals(that.registeredClientId) && principalName.equals(that.principalName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(registeredClientId, principalName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2022 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 sample.jpa.entity.client;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "`client`")
|
||||
public class Client {
|
||||
@Id
|
||||
private String id;
|
||||
private String clientId;
|
||||
private Instant clientIdIssuedAt;
|
||||
private String clientSecret;
|
||||
private Instant clientSecretExpiresAt;
|
||||
private String clientName;
|
||||
@Column(length = 1000)
|
||||
private String clientAuthenticationMethods;
|
||||
@Column(length = 1000)
|
||||
private String authorizationGrantTypes;
|
||||
@Column(length = 1000)
|
||||
private String redirectUris;
|
||||
@Column(length = 1000)
|
||||
private String scopes;
|
||||
@Column(length = 2000)
|
||||
private String clientSettings;
|
||||
@Column(length = 2000)
|
||||
private String tokenSettings;
|
||||
|
||||
// @fold:on
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public Instant getClientIdIssuedAt() {
|
||||
return clientIdIssuedAt;
|
||||
}
|
||||
|
||||
public void setClientIdIssuedAt(Instant clientIdIssuedAt) {
|
||||
this.clientIdIssuedAt = clientIdIssuedAt;
|
||||
}
|
||||
|
||||
public String getClientSecret() {
|
||||
return clientSecret;
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public Instant getClientSecretExpiresAt() {
|
||||
return clientSecretExpiresAt;
|
||||
}
|
||||
|
||||
public void setClientSecretExpiresAt(Instant clientSecretExpiresAt) {
|
||||
this.clientSecretExpiresAt = clientSecretExpiresAt;
|
||||
}
|
||||
|
||||
public String getClientName() {
|
||||
return clientName;
|
||||
}
|
||||
|
||||
public void setClientName(String clientName) {
|
||||
this.clientName = clientName;
|
||||
}
|
||||
|
||||
public String getClientAuthenticationMethods() {
|
||||
return clientAuthenticationMethods;
|
||||
}
|
||||
|
||||
public void setClientAuthenticationMethods(String clientAuthenticationMethods) {
|
||||
this.clientAuthenticationMethods = clientAuthenticationMethods;
|
||||
}
|
||||
|
||||
public String getAuthorizationGrantTypes() {
|
||||
return authorizationGrantTypes;
|
||||
}
|
||||
|
||||
public void setAuthorizationGrantTypes(String authorizationGrantTypes) {
|
||||
this.authorizationGrantTypes = authorizationGrantTypes;
|
||||
}
|
||||
|
||||
public String getRedirectUris() {
|
||||
return redirectUris;
|
||||
}
|
||||
|
||||
public void setRedirectUris(String redirectUris) {
|
||||
this.redirectUris = redirectUris;
|
||||
}
|
||||
|
||||
public String getScopes() {
|
||||
return scopes;
|
||||
}
|
||||
|
||||
public void setScopes(String scopes) {
|
||||
this.scopes = scopes;
|
||||
}
|
||||
|
||||
public String getClientSettings() {
|
||||
return clientSettings;
|
||||
}
|
||||
|
||||
public void setClientSettings(String clientSettings) {
|
||||
this.clientSettings = clientSettings;
|
||||
}
|
||||
|
||||
public String getTokenSettings() {
|
||||
return tokenSettings;
|
||||
}
|
||||
|
||||
public void setTokenSettings(String tokenSettings) {
|
||||
this.tokenSettings = tokenSettings;
|
||||
}
|
||||
// @fold:off
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2022 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 sample.jpa.repository.authorization;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import sample.jpa.entity.authorization.Authorization;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface AuthorizationRepository extends JpaRepository<Authorization, String> {
|
||||
Optional<Authorization> findByState(String state);
|
||||
Optional<Authorization> findByAuthorizationCodeValue(String authorizationCode);
|
||||
Optional<Authorization> findByAccessTokenValue(String accessToken);
|
||||
Optional<Authorization> findByRefreshTokenValue(String refreshToken);
|
||||
@Query("select a from Authorization a where a.state = :token" +
|
||||
" or a.authorizationCodeValue = :token" +
|
||||
" or a.accessTokenValue = :token" +
|
||||
" or a.refreshTokenValue = :token"
|
||||
)
|
||||
Optional<Authorization> findByStateOrAuthorizationCodeValueOrAccessTokenValueOrRefreshTokenValue(@Param("token") String token);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2022 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 sample.jpa.repository.authorizationConsent;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import sample.jpa.entity.authorizationConsent.AuthorizationConsent;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface AuthorizationConsentRepository extends JpaRepository<AuthorizationConsent, AuthorizationConsent.AuthorizationConsentId> {
|
||||
Optional<AuthorizationConsent> findByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName);
|
||||
void deleteByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2022 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 sample.jpa.repository.client;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import sample.jpa.entity.client.Client;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface ClientRepository extends JpaRepository<Client, String> {
|
||||
Optional<Client> findByClientId(String clientId);
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Copyright 2022 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 sample.jpa.service.authorization;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import sample.jpa.entity.authorization.Authorization;
|
||||
import sample.jpa.repository.authorization.AuthorizationRepository;
|
||||
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2Token;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
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.jackson2.OAuth2AuthorizationServerJackson2Module;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@Component
|
||||
public class JpaOAuth2AuthorizationService implements OAuth2AuthorizationService {
|
||||
private final AuthorizationRepository authorizationRepository;
|
||||
private final RegisteredClientRepository registeredClientRepository;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public JpaOAuth2AuthorizationService(AuthorizationRepository authorizationRepository, RegisteredClientRepository registeredClientRepository) {
|
||||
Assert.notNull(authorizationRepository, "authorizationRepository cannot be null");
|
||||
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
|
||||
this.authorizationRepository = authorizationRepository;
|
||||
this.registeredClientRepository = registeredClientRepository;
|
||||
|
||||
ClassLoader classLoader = JpaOAuth2AuthorizationService.class.getClassLoader();
|
||||
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
|
||||
this.objectMapper.registerModules(securityModules);
|
||||
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(OAuth2Authorization authorization) {
|
||||
Assert.notNull(authorization, "authorization cannot be null");
|
||||
this.authorizationRepository.save(toEntity(authorization));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(OAuth2Authorization authorization) {
|
||||
Assert.notNull(authorization, "authorization cannot be null");
|
||||
this.authorizationRepository.deleteById(authorization.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2Authorization findById(String id) {
|
||||
Assert.hasText(id, "id cannot be empty");
|
||||
return this.authorizationRepository.findById(id).map(this::toObject).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) {
|
||||
Assert.hasText(token, "token cannot be empty");
|
||||
|
||||
Optional<Authorization> result;
|
||||
if (tokenType == null) {
|
||||
result = this.authorizationRepository.findByStateOrAuthorizationCodeValueOrAccessTokenValueOrRefreshTokenValue(token);
|
||||
} else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
|
||||
result = this.authorizationRepository.findByState(token);
|
||||
} else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) {
|
||||
result = this.authorizationRepository.findByAuthorizationCodeValue(token);
|
||||
} else if (OAuth2ParameterNames.ACCESS_TOKEN.equals(tokenType.getValue())) {
|
||||
result = this.authorizationRepository.findByAccessTokenValue(token);
|
||||
} else if (OAuth2ParameterNames.REFRESH_TOKEN.equals(tokenType.getValue())) {
|
||||
result = this.authorizationRepository.findByRefreshTokenValue(token);
|
||||
} else {
|
||||
result = Optional.empty();
|
||||
}
|
||||
|
||||
return result.map(this::toObject).orElse(null);
|
||||
}
|
||||
|
||||
private OAuth2Authorization toObject(Authorization entity) {
|
||||
RegisteredClient registeredClient = this.registeredClientRepository.findById(entity.getRegisteredClientId());
|
||||
if (registeredClient == null) {
|
||||
throw new DataRetrievalFailureException(
|
||||
"The RegisteredClient with id '" + entity.getRegisteredClientId() + "' was not found in the RegisteredClientRepository.");
|
||||
}
|
||||
|
||||
OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.id(entity.getId())
|
||||
.principalName(entity.getPrincipalName())
|
||||
.authorizationGrantType(resolveAuthorizationGrantType(entity.getAuthorizationGrantType()))
|
||||
.authorizedScopes(StringUtils.commaDelimitedListToSet(entity.getAuthorizedScopes()))
|
||||
.attributes(attributes -> attributes.putAll(parseMap(entity.getAttributes())));
|
||||
if (entity.getState() != null) {
|
||||
builder.attribute(OAuth2ParameterNames.STATE, entity.getState());
|
||||
}
|
||||
|
||||
if (entity.getAuthorizationCodeValue() != null) {
|
||||
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
|
||||
entity.getAuthorizationCodeValue(),
|
||||
entity.getAuthorizationCodeIssuedAt(),
|
||||
entity.getAuthorizationCodeExpiresAt());
|
||||
builder.token(authorizationCode, metadata -> metadata.putAll(parseMap(entity.getAuthorizationCodeMetadata())));
|
||||
}
|
||||
|
||||
if (entity.getAccessTokenValue() != null) {
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER,
|
||||
entity.getAccessTokenValue(),
|
||||
entity.getAccessTokenIssuedAt(),
|
||||
entity.getAccessTokenExpiresAt(),
|
||||
StringUtils.commaDelimitedListToSet(entity.getAccessTokenScopes()));
|
||||
builder.token(accessToken, metadata -> metadata.putAll(parseMap(entity.getAccessTokenMetadata())));
|
||||
}
|
||||
|
||||
if (entity.getRefreshTokenValue() != null) {
|
||||
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(
|
||||
entity.getRefreshTokenValue(),
|
||||
entity.getRefreshTokenIssuedAt(),
|
||||
entity.getRefreshTokenExpiresAt());
|
||||
builder.token(refreshToken, metadata -> metadata.putAll(parseMap(entity.getRefreshTokenMetadata())));
|
||||
}
|
||||
|
||||
if (entity.getOidcIdTokenValue() != null) {
|
||||
OidcIdToken idToken = new OidcIdToken(
|
||||
entity.getOidcIdTokenValue(),
|
||||
entity.getOidcIdTokenIssuedAt(),
|
||||
entity.getOidcIdTokenExpiresAt(),
|
||||
parseMap(entity.getOidcIdTokenClaims()));
|
||||
builder.token(idToken, metadata -> metadata.putAll(parseMap(entity.getOidcIdTokenMetadata())));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private Authorization toEntity(OAuth2Authorization authorization) {
|
||||
Authorization entity = new Authorization();
|
||||
entity.setId(authorization.getId());
|
||||
entity.setRegisteredClientId(authorization.getRegisteredClientId());
|
||||
entity.setPrincipalName(authorization.getPrincipalName());
|
||||
entity.setAuthorizationGrantType(authorization.getAuthorizationGrantType().getValue());
|
||||
entity.setAuthorizedScopes(StringUtils.collectionToDelimitedString(authorization.getAuthorizedScopes(), ","));
|
||||
entity.setAttributes(writeMap(authorization.getAttributes()));
|
||||
entity.setState(authorization.getAttribute(OAuth2ParameterNames.STATE));
|
||||
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode =
|
||||
authorization.getToken(OAuth2AuthorizationCode.class);
|
||||
setTokenValues(
|
||||
authorizationCode,
|
||||
entity::setAuthorizationCodeValue,
|
||||
entity::setAuthorizationCodeIssuedAt,
|
||||
entity::setAuthorizationCodeExpiresAt,
|
||||
entity::setAuthorizationCodeMetadata
|
||||
);
|
||||
|
||||
OAuth2Authorization.Token<OAuth2AccessToken> accessToken =
|
||||
authorization.getToken(OAuth2AccessToken.class);
|
||||
setTokenValues(
|
||||
accessToken,
|
||||
entity::setAccessTokenValue,
|
||||
entity::setAccessTokenIssuedAt,
|
||||
entity::setAccessTokenExpiresAt,
|
||||
entity::setAccessTokenMetadata
|
||||
);
|
||||
if (accessToken != null && accessToken.getToken().getScopes() != null) {
|
||||
entity.setAccessTokenScopes(StringUtils.collectionToDelimitedString(accessToken.getToken().getScopes(), ","));
|
||||
}
|
||||
|
||||
OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken =
|
||||
authorization.getToken(OAuth2RefreshToken.class);
|
||||
setTokenValues(
|
||||
refreshToken,
|
||||
entity::setRefreshTokenValue,
|
||||
entity::setRefreshTokenIssuedAt,
|
||||
entity::setRefreshTokenExpiresAt,
|
||||
entity::setRefreshTokenMetadata
|
||||
);
|
||||
|
||||
OAuth2Authorization.Token<OidcIdToken> oidcIdToken =
|
||||
authorization.getToken(OidcIdToken.class);
|
||||
setTokenValues(
|
||||
oidcIdToken,
|
||||
entity::setOidcIdTokenValue,
|
||||
entity::setOidcIdTokenIssuedAt,
|
||||
entity::setOidcIdTokenExpiresAt,
|
||||
entity::setOidcIdTokenMetadata
|
||||
);
|
||||
if (oidcIdToken != null) {
|
||||
entity.setOidcIdTokenClaims(writeMap(oidcIdToken.getClaims()));
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void setTokenValues(
|
||||
OAuth2Authorization.Token<?> token,
|
||||
Consumer<String> tokenValueConsumer,
|
||||
Consumer<Instant> issuedAtConsumer,
|
||||
Consumer<Instant> expiresAtConsumer,
|
||||
Consumer<String> metadataConsumer) {
|
||||
if (token != null) {
|
||||
OAuth2Token oAuth2Token = token.getToken();
|
||||
tokenValueConsumer.accept(oAuth2Token.getTokenValue());
|
||||
issuedAtConsumer.accept(oAuth2Token.getIssuedAt());
|
||||
expiresAtConsumer.accept(oAuth2Token.getExpiresAt());
|
||||
metadataConsumer.accept(writeMap(token.getMetadata()));
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> parseMap(String data) {
|
||||
try {
|
||||
return this.objectMapper.readValue(data, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String writeMap(Map<String, Object> metadata) {
|
||||
try {
|
||||
return this.objectMapper.writeValueAsString(metadata);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
|
||||
return AuthorizationGrantType.AUTHORIZATION_CODE;
|
||||
} else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(authorizationGrantType)) {
|
||||
return AuthorizationGrantType.CLIENT_CREDENTIALS;
|
||||
} else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) {
|
||||
return AuthorizationGrantType.REFRESH_TOKEN;
|
||||
}
|
||||
return new AuthorizationGrantType(authorizationGrantType); // Custom authorization grant type
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2022 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 sample.jpa.service.authorizationConsent;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import sample.jpa.entity.authorizationConsent.AuthorizationConsent;
|
||||
import sample.jpa.repository.authorizationConsent.AuthorizationConsentRepository;
|
||||
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@Component
|
||||
public class JpaOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {
|
||||
private final AuthorizationConsentRepository authorizationConsentRepository;
|
||||
private final RegisteredClientRepository registeredClientRepository;
|
||||
|
||||
public JpaOAuth2AuthorizationConsentService(AuthorizationConsentRepository authorizationConsentRepository, RegisteredClientRepository registeredClientRepository) {
|
||||
Assert.notNull(authorizationConsentRepository, "authorizationConsentRepository cannot be null");
|
||||
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
|
||||
this.authorizationConsentRepository = authorizationConsentRepository;
|
||||
this.registeredClientRepository = registeredClientRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
||||
this.authorizationConsentRepository.save(toEntity(authorizationConsent));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
||||
this.authorizationConsentRepository.deleteByRegisteredClientIdAndPrincipalName(
|
||||
authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) {
|
||||
Assert.hasText(registeredClientId, "registeredClientId cannot be empty");
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
return this.authorizationConsentRepository.findByRegisteredClientIdAndPrincipalName(
|
||||
registeredClientId, principalName).map(this::toObject).orElse(null);
|
||||
}
|
||||
|
||||
private OAuth2AuthorizationConsent toObject(AuthorizationConsent authorizationConsent) {
|
||||
String registeredClientId = authorizationConsent.getRegisteredClientId();
|
||||
RegisteredClient registeredClient = this.registeredClientRepository.findById(registeredClientId);
|
||||
if (registeredClient == null) {
|
||||
throw new DataRetrievalFailureException(
|
||||
"The RegisteredClient with id '" + registeredClientId + "' was not found in the RegisteredClientRepository.");
|
||||
}
|
||||
|
||||
OAuth2AuthorizationConsent.Builder builder = OAuth2AuthorizationConsent.withId(
|
||||
registeredClientId, authorizationConsent.getPrincipalName());
|
||||
if (authorizationConsent.getAuthorities() != null) {
|
||||
for (String authority : StringUtils.commaDelimitedListToSet(authorizationConsent.getAuthorities())) {
|
||||
builder.authority(new SimpleGrantedAuthority(authority));
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private AuthorizationConsent toEntity(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
AuthorizationConsent entity = new AuthorizationConsent();
|
||||
entity.setRegisteredClientId(authorizationConsent.getRegisteredClientId());
|
||||
entity.setPrincipalName(authorizationConsent.getPrincipalName());
|
||||
|
||||
Set<String> authorities = new HashSet<>();
|
||||
for (GrantedAuthority authority : authorizationConsent.getAuthorities()) {
|
||||
authorities.add(authority.getAuthority());
|
||||
}
|
||||
entity.setAuthorities(StringUtils.collectionToCommaDelimitedString(authorities));
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2022 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 sample.jpa.service.client;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import sample.jpa.entity.client.Client;
|
||||
import sample.jpa.repository.client.ClientRepository;
|
||||
|
||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
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.jackson2.OAuth2AuthorizationServerJackson2Module;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@Component
|
||||
public class JpaRegisteredClientRepository implements RegisteredClientRepository {
|
||||
private final ClientRepository clientRepository;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public JpaRegisteredClientRepository(ClientRepository clientRepository) {
|
||||
Assert.notNull(clientRepository, "clientRepository cannot be null");
|
||||
this.clientRepository = clientRepository;
|
||||
|
||||
ClassLoader classLoader = JpaRegisteredClientRepository.class.getClassLoader();
|
||||
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
|
||||
this.objectMapper.registerModules(securityModules);
|
||||
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(RegisteredClient registeredClient) {
|
||||
Assert.notNull(registeredClient, "registeredClient cannot be null");
|
||||
this.clientRepository.save(toEntity(registeredClient));
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegisteredClient findById(String id) {
|
||||
Assert.hasText(id, "id cannot be empty");
|
||||
return this.clientRepository.findById(id).map(this::toObject).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegisteredClient findByClientId(String clientId) {
|
||||
Assert.hasText(clientId, "clientId cannot be empty");
|
||||
return this.clientRepository.findByClientId(clientId).map(this::toObject).orElse(null);
|
||||
}
|
||||
|
||||
private RegisteredClient toObject(Client client) {
|
||||
Set<String> clientAuthenticationMethods = StringUtils.commaDelimitedListToSet(
|
||||
client.getClientAuthenticationMethods());
|
||||
Set<String> authorizationGrantTypes = StringUtils.commaDelimitedListToSet(
|
||||
client.getAuthorizationGrantTypes());
|
||||
Set<String> redirectUris = StringUtils.commaDelimitedListToSet(
|
||||
client.getRedirectUris());
|
||||
Set<String> clientScopes = StringUtils.commaDelimitedListToSet(
|
||||
client.getScopes());
|
||||
|
||||
RegisteredClient.Builder builder = RegisteredClient.withId(client.getId())
|
||||
.clientId(client.getClientId())
|
||||
.clientIdIssuedAt(client.getClientIdIssuedAt())
|
||||
.clientSecret(client.getClientSecret())
|
||||
.clientSecretExpiresAt(client.getClientSecretExpiresAt())
|
||||
.clientName(client.getClientName())
|
||||
.clientAuthenticationMethods(authenticationMethods ->
|
||||
clientAuthenticationMethods.forEach(authenticationMethod ->
|
||||
authenticationMethods.add(resolveClientAuthenticationMethod(authenticationMethod))))
|
||||
.authorizationGrantTypes((grantTypes) ->
|
||||
authorizationGrantTypes.forEach(grantType ->
|
||||
grantTypes.add(resolveAuthorizationGrantType(grantType))))
|
||||
.redirectUris((uris) -> uris.addAll(redirectUris))
|
||||
.scopes((scopes) -> scopes.addAll(clientScopes));
|
||||
|
||||
Map<String, Object> clientSettingsMap = parseMap(client.getClientSettings());
|
||||
builder.clientSettings(ClientSettings.withSettings(clientSettingsMap).build());
|
||||
|
||||
Map<String, Object> tokenSettingsMap = parseMap(client.getTokenSettings());
|
||||
builder.tokenSettings(TokenSettings.withSettings(tokenSettingsMap).build());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private Client toEntity(RegisteredClient registeredClient) {
|
||||
List<String> clientAuthenticationMethods = new ArrayList<>(registeredClient.getClientAuthenticationMethods().size());
|
||||
registeredClient.getClientAuthenticationMethods().forEach(clientAuthenticationMethod ->
|
||||
clientAuthenticationMethods.add(clientAuthenticationMethod.getValue()));
|
||||
|
||||
List<String> authorizationGrantTypes = new ArrayList<>(registeredClient.getAuthorizationGrantTypes().size());
|
||||
registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
|
||||
authorizationGrantTypes.add(authorizationGrantType.getValue()));
|
||||
|
||||
Client entity = new Client();
|
||||
entity.setId(registeredClient.getId());
|
||||
entity.setClientId(registeredClient.getClientId());
|
||||
entity.setClientIdIssuedAt(registeredClient.getClientIdIssuedAt());
|
||||
entity.setClientSecret(registeredClient.getClientSecret());
|
||||
entity.setClientSecretExpiresAt(registeredClient.getClientSecretExpiresAt());
|
||||
entity.setClientName(registeredClient.getClientName());
|
||||
entity.setClientAuthenticationMethods(StringUtils.collectionToCommaDelimitedString(clientAuthenticationMethods));
|
||||
entity.setAuthorizationGrantTypes(StringUtils.collectionToCommaDelimitedString(authorizationGrantTypes));
|
||||
entity.setRedirectUris(StringUtils.collectionToCommaDelimitedString(registeredClient.getRedirectUris()));
|
||||
entity.setScopes(StringUtils.collectionToCommaDelimitedString(registeredClient.getScopes()));
|
||||
entity.setClientSettings(writeMap(registeredClient.getClientSettings().getSettings()));
|
||||
entity.setTokenSettings(writeMap(registeredClient.getTokenSettings().getSettings()));
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
private Map<String, Object> parseMap(String data) {
|
||||
try {
|
||||
return this.objectMapper.readValue(data, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String writeMap(Map<String, Object> data) {
|
||||
try {
|
||||
return this.objectMapper.writeValueAsString(data);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
|
||||
return AuthorizationGrantType.AUTHORIZATION_CODE;
|
||||
} else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(authorizationGrantType)) {
|
||||
return AuthorizationGrantType.CLIENT_CREDENTIALS;
|
||||
} else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) {
|
||||
return AuthorizationGrantType.REFRESH_TOKEN;
|
||||
}
|
||||
return new AuthorizationGrantType(authorizationGrantType); // Custom authorization grant type
|
||||
}
|
||||
|
||||
private static ClientAuthenticationMethod resolveClientAuthenticationMethod(String clientAuthenticationMethod) {
|
||||
if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue().equals(clientAuthenticationMethod)) {
|
||||
return ClientAuthenticationMethod.CLIENT_SECRET_BASIC;
|
||||
} else if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientAuthenticationMethod)) {
|
||||
return ClientAuthenticationMethod.CLIENT_SECRET_POST;
|
||||
} else if (ClientAuthenticationMethod.NONE.getValue().equals(clientAuthenticationMethod)) {
|
||||
return ClientAuthenticationMethod.NONE;
|
||||
}
|
||||
return new ClientAuthenticationMethod(clientAuthenticationMethod); // Custom client authentication method
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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 sample.userinfo;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
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.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
||||
|
||||
@Configuration
|
||||
public class EnableUserInfoSecurityConfig {
|
||||
|
||||
@Bean // <1>
|
||||
@Order(1)
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
|
||||
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
|
||||
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
|
||||
// @formatter:off
|
||||
http
|
||||
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) // <2>
|
||||
.exceptionHandling((exceptions) -> exceptions
|
||||
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
// @fold:on
|
||||
@Bean
|
||||
@Order(2)
|
||||
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(Customizer.withDefaults());
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
// @fold:off
|
||||
|
||||
@Bean // <3>
|
||||
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
|
||||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
||||
}
|
||||
|
||||
// @fold:on
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
// @formatter:off
|
||||
UserDetails userDetails = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
return new InMemoryUserDetailsManager(userDetails);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RegisteredClientRepository registeredClientRepository() {
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
|
||||
.clientId("messaging-client")
|
||||
.clientSecret("{noop}secret")
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
|
||||
.redirectUri("http://127.0.0.1:8080/authorized")
|
||||
.scope(OidcScopes.OPENID)
|
||||
.scope(OidcScopes.ADDRESS)
|
||||
.scope(OidcScopes.EMAIL)
|
||||
.scope(OidcScopes.PHONE)
|
||||
.scope(OidcScopes.PROFILE)
|
||||
.scope("message.read")
|
||||
.scope("message.write")
|
||||
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
return new InMemoryRegisteredClientRepository(registeredClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JWKSource<SecurityContext> jwkSource() {
|
||||
KeyPair keyPair = generateRsaKey();
|
||||
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
|
||||
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
|
||||
// @formatter:off
|
||||
RSAKey rsaKey = new RSAKey.Builder(publicKey)
|
||||
.privateKey(privateKey)
|
||||
.keyID(UUID.randomUUID().toString())
|
||||
.build();
|
||||
// @formatter:on
|
||||
JWKSet jwkSet = new JWKSet(rsaKey);
|
||||
return new ImmutableJWKSet<>(jwkSet);
|
||||
}
|
||||
|
||||
private static KeyPair generateRsaKey() {
|
||||
KeyPair keyPair;
|
||||
try {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(2048);
|
||||
keyPair = keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder().build();
|
||||
}
|
||||
// @fold:off
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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 sample.userinfo.idtoken;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
|
||||
|
||||
@Configuration
|
||||
public class IdTokenCustomizerConfig {
|
||||
|
||||
// @formatter:off
|
||||
@Bean // <1>
|
||||
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer(
|
||||
OidcUserInfoService userInfoService) {
|
||||
return (context) -> {
|
||||
if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) {
|
||||
OidcUserInfo userInfo = userInfoService.loadUser( // <2>
|
||||
context.getPrincipal().getName());
|
||||
context.getClaims().claims(claims ->
|
||||
claims.putAll(userInfo.getClaims()));
|
||||
}
|
||||
};
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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 sample.userinfo.idtoken;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Example service to perform lookup of user info for customizing an {@code id_token}.
|
||||
*/
|
||||
@Service
|
||||
public class OidcUserInfoService {
|
||||
|
||||
private final UserInfoRepository userInfoRepository = new UserInfoRepository();
|
||||
|
||||
public OidcUserInfo loadUser(String username) {
|
||||
return new OidcUserInfo(this.userInfoRepository.findByUsername(username));
|
||||
}
|
||||
|
||||
static class UserInfoRepository {
|
||||
|
||||
private final Map<String, Map<String, Object>> userInfo = new HashMap<>();
|
||||
|
||||
public UserInfoRepository() {
|
||||
this.userInfo.put("user1", createUser("user1"));
|
||||
this.userInfo.put("user2", createUser("user2"));
|
||||
}
|
||||
|
||||
public Map<String, Object> findByUsername(String username) {
|
||||
return this.userInfo.get(username);
|
||||
}
|
||||
|
||||
private static Map<String, Object> createUser(String username) {
|
||||
return OidcUserInfo.builder()
|
||||
.subject(username)
|
||||
.name("First Last")
|
||||
.givenName("First")
|
||||
.familyName("Last")
|
||||
.middleName("Middle")
|
||||
.nickname("User")
|
||||
.preferredUsername(username)
|
||||
.profile("https://example.com/" + username)
|
||||
.picture("https://example.com/" + username + ".jpg")
|
||||
.website("https://example.com")
|
||||
.email(username + "@example.com")
|
||||
.emailVerified(true)
|
||||
.gender("female")
|
||||
.birthdate("1970-01-01")
|
||||
.zoneinfo("Europe/Paris")
|
||||
.locale("en-US")
|
||||
.phoneNumber("+1 (604) 555-1234;ext=5678")
|
||||
.claim("address", Collections.singletonMap("formatted", "Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"))
|
||||
.updatedAt("1970-01-01T00:00:00Z")
|
||||
.build()
|
||||
.getClaims();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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 sample.userinfo.jwt;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
|
||||
|
||||
@Configuration
|
||||
public class JwtTokenCustomizerConfig {
|
||||
|
||||
// @formatter:off
|
||||
@Bean
|
||||
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
|
||||
return (context) -> {
|
||||
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
|
||||
context.getClaims().claims((claims) -> {
|
||||
claims.put("claim-1", "value-1");
|
||||
claims.put("claim-2", "value-2");
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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 sample.userinfo.jwt;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
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.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
|
||||
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
|
||||
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
@Configuration
|
||||
public class JwtUserInfoMapperSecurityConfig {
|
||||
|
||||
@Bean // <1>
|
||||
@Order(1)
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
RequestMatcher endpointsMatcher = authorizationServerConfigurer
|
||||
.getEndpointsMatcher();
|
||||
|
||||
Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper = (context) -> { // <2>
|
||||
OidcUserInfoAuthenticationToken authentication = context.getAuthentication();
|
||||
JwtAuthenticationToken principal = (JwtAuthenticationToken) authentication.getPrincipal();
|
||||
|
||||
return new OidcUserInfo(principal.getToken().getClaims());
|
||||
};
|
||||
|
||||
// @formatter:off
|
||||
authorizationServerConfigurer
|
||||
.oidc((oidc) -> oidc
|
||||
.userInfoEndpoint((userInfo) -> userInfo
|
||||
.userInfoMapper(userInfoMapper) // <3>
|
||||
)
|
||||
);
|
||||
http
|
||||
.requestMatcher(endpointsMatcher)
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
|
||||
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) // <4>
|
||||
.exceptionHandling((exceptions) -> exceptions
|
||||
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
|
||||
)
|
||||
.apply(authorizationServerConfigurer); // <5>
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
// @fold:on
|
||||
@Bean
|
||||
@Order(2)
|
||||
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(Customizer.withDefaults());
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
|
||||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
// @formatter:off
|
||||
UserDetails userDetails = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
return new InMemoryUserDetailsManager(userDetails);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RegisteredClientRepository registeredClientRepository() {
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
|
||||
.clientId("messaging-client")
|
||||
.clientSecret("{noop}secret")
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
|
||||
.redirectUri("http://127.0.0.1:8080/authorized")
|
||||
.scope(OidcScopes.OPENID)
|
||||
.scope("message.read")
|
||||
.scope("message.write")
|
||||
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
return new InMemoryRegisteredClientRepository(registeredClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JWKSource<SecurityContext> jwkSource() {
|
||||
KeyPair keyPair = generateRsaKey();
|
||||
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
|
||||
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
|
||||
// @formatter:off
|
||||
RSAKey rsaKey = new RSAKey.Builder(publicKey)
|
||||
.privateKey(privateKey)
|
||||
.keyID(UUID.randomUUID().toString())
|
||||
.build();
|
||||
// @formatter:on
|
||||
JWKSet jwkSet = new JWKSet(rsaKey);
|
||||
return new ImmutableJWKSet<>(jwkSet);
|
||||
}
|
||||
|
||||
private static KeyPair generateRsaKey() {
|
||||
KeyPair keyPair;
|
||||
try {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(2048);
|
||||
keyPair = keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder().build();
|
||||
}
|
||||
// @fold:off
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
server:
|
||||
port: 9000
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.springframework.security: trace
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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 sample;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* Helper class that performs steps of the {@code authorization_code} flow using
|
||||
* {@link MockMvc} for testing.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class AuthorizationCodeGrantFlow {
|
||||
private static final Pattern HIDDEN_STATE_INPUT_PATTERN = Pattern.compile(".+<input type=\"hidden\" name=\"state\" value=\"([^\"]+)\">.+");
|
||||
private static final TypeReference<Map<String, Object>> TOKEN_RESPONSE_TYPE_REFERENCE = new TypeReference<Map<String, Object>>() {
|
||||
};
|
||||
|
||||
private final MockMvc mockMvc;
|
||||
|
||||
private String username = "user";
|
||||
|
||||
private Set<String> scopes = new HashSet<>();
|
||||
|
||||
public AuthorizationCodeGrantFlow(MockMvc mockMvc) {
|
||||
this.mockMvc = mockMvc;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public void addScope(String scope) {
|
||||
this.scopes.add(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the authorization request and obtain a state parameter.
|
||||
*
|
||||
* @param registeredClient The registered client
|
||||
* @return The state parameter for submitting consent for authorization
|
||||
*/
|
||||
public String authorize(RegisteredClient registeredClient) throws Exception {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue());
|
||||
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
||||
parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next());
|
||||
parameters.set(OAuth2ParameterNames.SCOPE,
|
||||
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
|
||||
parameters.set(OAuth2ParameterNames.STATE, "state");
|
||||
|
||||
MvcResult mvcResult = this.mockMvc.perform(get("/oauth2/authorize")
|
||||
.params(parameters)
|
||||
.with(user(this.username).roles("USER")))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string("content-type", containsString(MediaType.TEXT_HTML_VALUE)))
|
||||
.andReturn();
|
||||
String responseHtml = mvcResult.getResponse().getContentAsString();
|
||||
Matcher matcher = HIDDEN_STATE_INPUT_PATTERN.matcher(responseHtml);
|
||||
|
||||
return matcher.matches() ? matcher.group(1) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit consent for the authorization request and obtain an authorization code.
|
||||
*
|
||||
* @param registeredClient The registered client
|
||||
* @param state The state paramter from the authorization request
|
||||
* @return An authorization code
|
||||
*/
|
||||
public String submitConsent(RegisteredClient registeredClient, String state) throws Exception {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
||||
parameters.set(OAuth2ParameterNames.STATE, state);
|
||||
for (String scope : scopes) {
|
||||
parameters.add(OAuth2ParameterNames.SCOPE, scope);
|
||||
}
|
||||
|
||||
MvcResult mvcResult = this.mockMvc.perform(post("/oauth2/authorize")
|
||||
.params(parameters)
|
||||
.with(user(this.username).roles("USER")))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
|
||||
assertThat(redirectedUrl).isNotNull();
|
||||
assertThat(redirectedUrl).matches("http://127.0.0.1:8080/authorized\\?code=.{15,}&state=state");
|
||||
|
||||
String locationHeader = URLDecoder.decode(redirectedUrl, StandardCharsets.UTF_8.name());
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build();
|
||||
|
||||
return uriComponents.getQueryParams().getFirst("code");
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchange an authorization code for an access token.
|
||||
*
|
||||
* @param registeredClient The registered client
|
||||
* @param authorizationCode The authorization code obtained from the authorization request
|
||||
* @return The token response
|
||||
*/
|
||||
public Map<String, Object> getTokenResponse(RegisteredClient registeredClient, String authorizationCode) throws Exception {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
|
||||
parameters.set(OAuth2ParameterNames.CODE, authorizationCode);
|
||||
parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next());
|
||||
|
||||
HttpHeaders basicAuth = new HttpHeaders();
|
||||
basicAuth.setBasicAuth(registeredClient.getClientId(), "secret");
|
||||
|
||||
MvcResult mvcResult = this.mockMvc.perform(post("/oauth2/token")
|
||||
.params(parameters)
|
||||
.headers(basicAuth))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, containsString(MediaType.APPLICATION_JSON_VALUE)))
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.token_type").isNotEmpty())
|
||||
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||
.andExpect(jsonPath("$.refresh_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.scope").isNotEmpty())
|
||||
.andExpect(jsonPath("$.id_token").isNotEmpty())
|
||||
.andReturn();
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String responseJson = mvcResult.getResponse().getContentAsString();
|
||||
return objectMapper.readValue(responseJson, TOKEN_RESPONSE_TYPE_REFERENCE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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 sample.gettingStarted;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.api.ObjectAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import sample.AuthorizationCodeGrantFlow;
|
||||
import sample.test.SpringTestContext;
|
||||
import sample.test.SpringTestContextExtension;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
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.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for the Getting Started section of the reference documentation.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class SecurityConfigTests {
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private RegisteredClientRepository registeredClientRepository;
|
||||
|
||||
@Autowired
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
|
||||
@Autowired
|
||||
private OAuth2AuthorizationConsentService authorizationConsentService;
|
||||
|
||||
@Test
|
||||
public void oidcLoginWhenGettingStartedConfigUsedThenSuccess() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfig.class).autowire();
|
||||
assertThat(this.registeredClientRepository).isInstanceOf(InMemoryRegisteredClientRepository.class);
|
||||
assertThat(this.authorizationService).isInstanceOf(InMemoryOAuth2AuthorizationService.class);
|
||||
assertThat(this.authorizationConsentService).isInstanceOf(InMemoryOAuth2AuthorizationConsentService.class);
|
||||
|
||||
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId("messaging-client");
|
||||
assertThat(registeredClient).isNotNull();
|
||||
|
||||
AuthorizationCodeGrantFlow authorizationCodeGrantFlow = new AuthorizationCodeGrantFlow(this.mockMvc);
|
||||
authorizationCodeGrantFlow.setUsername("user");
|
||||
authorizationCodeGrantFlow.addScope("message.read");
|
||||
authorizationCodeGrantFlow.addScope("message.write");
|
||||
|
||||
String state = authorizationCodeGrantFlow.authorize(registeredClient);
|
||||
assertThatAuthorization(state, OAuth2ParameterNames.STATE).isNotNull();
|
||||
assertThatAuthorization(state, null).isNotNull();
|
||||
|
||||
String authorizationCode = authorizationCodeGrantFlow.submitConsent(registeredClient, state);
|
||||
assertThatAuthorization(authorizationCode, OAuth2ParameterNames.CODE).isNotNull();
|
||||
assertThatAuthorization(authorizationCode, null).isNotNull();
|
||||
|
||||
Map<String, Object> tokenResponse = authorizationCodeGrantFlow.getTokenResponse(registeredClient, authorizationCode);
|
||||
String accessToken = (String) tokenResponse.get(OAuth2ParameterNames.ACCESS_TOKEN);
|
||||
assertThatAuthorization(accessToken, OAuth2ParameterNames.ACCESS_TOKEN).isNotNull();
|
||||
assertThatAuthorization(accessToken, null).isNotNull();
|
||||
|
||||
String refreshToken = (String) tokenResponse.get(OAuth2ParameterNames.REFRESH_TOKEN);
|
||||
assertThatAuthorization(refreshToken, OAuth2ParameterNames.REFRESH_TOKEN).isNotNull();
|
||||
assertThatAuthorization(refreshToken, null).isNotNull();
|
||||
|
||||
String idToken = (String) tokenResponse.get(OidcParameterNames.ID_TOKEN);
|
||||
assertThatAuthorization(idToken, OidcParameterNames.ID_TOKEN).isNull(); // id_token is not searchable
|
||||
|
||||
OAuth2Authorization authorization = findAuthorization(accessToken, OAuth2ParameterNames.ACCESS_TOKEN);
|
||||
assertThat(authorization.getToken(idToken)).isNotNull();
|
||||
|
||||
String scopes = (String) tokenResponse.get(OAuth2ParameterNames.SCOPE);
|
||||
OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService.findById(
|
||||
registeredClient.getId(), "user");
|
||||
assertThat(authorizationConsent).isNotNull();
|
||||
assertThat(authorizationConsent.getScopes()).containsExactlyInAnyOrder(
|
||||
StringUtils.delimitedListToStringArray(scopes, " "));
|
||||
}
|
||||
|
||||
private ObjectAssert<OAuth2Authorization> assertThatAuthorization(String token, String tokenType) {
|
||||
return assertThat(findAuthorization(token, tokenType));
|
||||
}
|
||||
|
||||
private OAuth2Authorization findAuthorization(String token, String tokenType) {
|
||||
return this.authorizationService.findByToken(token, tokenType == null ? null : new OAuth2TokenType(tokenType));
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@EnableAutoConfiguration
|
||||
@ComponentScan
|
||||
@Import(OAuth2AuthorizationServerConfiguration.class)
|
||||
static class AuthorizationServerConfig extends SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public OAuth2AuthorizationService authorizationService() {
|
||||
return new InMemoryOAuth2AuthorizationService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2AuthorizationConsentService authorizationConsentService() {
|
||||
return new InMemoryOAuth2AuthorizationConsentService();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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 sample.jose;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import com.nimbusds.jose.jwk.Curve;
|
||||
import com.nimbusds.jose.jwk.ECKey;
|
||||
import com.nimbusds.jose.jwk.KeyUse;
|
||||
import com.nimbusds.jose.jwk.OctetSequenceKey;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public final class TestJwks {
|
||||
|
||||
private static final KeyPairGenerator rsaKeyPairGenerator;
|
||||
static {
|
||||
try {
|
||||
rsaKeyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
rsaKeyPairGenerator.initialize(2048);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
public static final RSAKey DEFAULT_RSA_JWK =
|
||||
jwk(
|
||||
TestKeys.DEFAULT_PUBLIC_KEY,
|
||||
TestKeys.DEFAULT_PRIVATE_KEY
|
||||
).build();
|
||||
// @formatter:on
|
||||
|
||||
// @formatter:off
|
||||
public static final ECKey DEFAULT_EC_JWK =
|
||||
jwk(
|
||||
(ECPublicKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPublic(),
|
||||
(ECPrivateKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPrivate()
|
||||
).build();
|
||||
// @formatter:on
|
||||
|
||||
// @formatter:off
|
||||
public static final OctetSequenceKey DEFAULT_SECRET_JWK =
|
||||
jwk(
|
||||
TestKeys.DEFAULT_SECRET_KEY
|
||||
).build();
|
||||
// @formatter:on
|
||||
|
||||
private TestJwks() {
|
||||
}
|
||||
|
||||
public static RSAKey.Builder generateRsaJwk() {
|
||||
KeyPair keyPair = rsaKeyPairGenerator.generateKeyPair();
|
||||
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
|
||||
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
|
||||
// @formatter:off
|
||||
return jwk(publicKey, privateKey)
|
||||
.keyID(UUID.randomUUID().toString());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static RSAKey.Builder jwk(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
|
||||
// @formatter:off
|
||||
return new RSAKey.Builder(publicKey)
|
||||
.privateKey(privateKey)
|
||||
.keyUse(KeyUse.SIGNATURE)
|
||||
.keyID("rsa-jwk-kid");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static ECKey.Builder jwk(ECPublicKey publicKey, ECPrivateKey privateKey) {
|
||||
// @formatter:off
|
||||
Curve curve = Curve.forECParameterSpec(publicKey.getParams());
|
||||
return new ECKey.Builder(curve, publicKey)
|
||||
.privateKey(privateKey)
|
||||
.keyUse(KeyUse.SIGNATURE)
|
||||
.keyID("ec-jwk-kid");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static OctetSequenceKey.Builder jwk(SecretKey secretKey) {
|
||||
// @formatter:off
|
||||
return new OctetSequenceKey.Builder(secretKey)
|
||||
.keyUse(KeyUse.SIGNATURE)
|
||||
.keyID("secret-jwk-kid");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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 sample.jose;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.ECFieldFp;
|
||||
import java.security.spec.ECParameterSpec;
|
||||
import java.security.spec.ECPoint;
|
||||
import java.security.spec.EllipticCurve;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public final class TestKeys {
|
||||
|
||||
public static final KeyFactory kf;
|
||||
static {
|
||||
try {
|
||||
kf = KeyFactory.getInstance("RSA");
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
public static final String DEFAULT_ENCODED_SECRET_KEY = "bCzY/M48bbkwBEWjmNSIEPfwApcvXOnkCxORBEbPr+4=";
|
||||
|
||||
public static final SecretKey DEFAULT_SECRET_KEY = new SecretKeySpec(
|
||||
Base64.getDecoder().decode(DEFAULT_ENCODED_SECRET_KEY), "AES");
|
||||
|
||||
// @formatter:off
|
||||
public static final String DEFAULT_RSA_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3FlqJr5TRskIQIgdE3Dd"
|
||||
+ "7D9lboWdcTUT8a+fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRv"
|
||||
+ "c5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4/1tfRgG6ii4Uhxh6"
|
||||
+ "iI8qNMJQX+fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2"
|
||||
+ "kJdJ/ZIV+WW4noDdzpKqHcwmB8FsrumlVY/DNVvUSDIipiq9PbP4H99TXN1o746o"
|
||||
+ "RaNa07rq1hoCgMSSy+85SagCoxlmyE+D+of9SsMY8Ol9t0rdzpobBuhyJ/o5dfvj"
|
||||
+ "KwIDAQAB";
|
||||
// @formatter:on
|
||||
|
||||
public static final RSAPublicKey DEFAULT_PUBLIC_KEY;
|
||||
static {
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.getDecoder().decode(DEFAULT_RSA_PUBLIC_KEY));
|
||||
try {
|
||||
DEFAULT_PUBLIC_KEY = (RSAPublicKey) kf.generatePublic(spec);
|
||||
}
|
||||
catch (InvalidKeySpecException ex) {
|
||||
throw new IllegalArgumentException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
public static final String DEFAULT_RSA_PRIVATE_KEY = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcWWomvlNGyQhA"
|
||||
+ "iB0TcN3sP2VuhZ1xNRPxr58lHswC9Cbtdc2hiSbe/sxAvU1i0O8vaXwICdzRZ1JM"
|
||||
+ "g1TohG9zkqqjZDhyw1f1Ic6YR/OhE6NCpqERy97WMFeW6gJd1i5inHj/W19GAbqK"
|
||||
+ "LhSHGHqIjyo0wlBf58t+qFt9h/EFBVE/LAGQBsg/jHUQCxsLoVI2aSELGIw2oSDF"
|
||||
+ "oiljwLaQl0n9khX5ZbiegN3OkqodzCYHwWyu6aVVj8M1W9RIMiKmKr09s/gf31Nc"
|
||||
+ "3WjvjqhFo1rTuurWGgKAxJLL7zlJqAKjGWbIT4P6h/1Kwxjw6X23St3OmhsG6HIn"
|
||||
+ "+jl1++MrAgMBAAECggEBAMf820wop3pyUOwI3aLcaH7YFx5VZMzvqJdNlvpg1jbE"
|
||||
+ "E2Sn66b1zPLNfOIxLcBG8x8r9Ody1Bi2Vsqc0/5o3KKfdgHvnxAB3Z3dPh2WCDek"
|
||||
+ "lCOVClEVoLzziTuuTdGO5/CWJXdWHcVzIjPxmK34eJXioiLaTYqN3XKqKMdpD0ZG"
|
||||
+ "mtNTGvGf+9fQ4i94t0WqIxpMpGt7NM4RHy3+Onggev0zLiDANC23mWrTsUgect/7"
|
||||
+ "62TYg8g1bKwLAb9wCBT+BiOuCc2wrArRLOJgUkj/F4/gtrR9ima34SvWUyoUaKA0"
|
||||
+ "bi4YBX9l8oJwFGHbU9uFGEMnH0T/V0KtIB7qetReywkCgYEA9cFyfBIQrYISV/OA"
|
||||
+ "+Z0bo3vh2aL0QgKrSXZ924cLt7itQAHNZ2ya+e3JRlTczi5mnWfjPWZ6eJB/8MlH"
|
||||
+ "Gpn12o/POEkU+XjZZSPe1RWGt5g0S3lWqyx9toCS9ACXcN9tGbaqcFSVI73zVTRA"
|
||||
+ "8J9grR0fbGn7jaTlTX2tnlOTQ60CgYEA5YjYpEq4L8UUMFkuj+BsS3u0oEBnzuHd"
|
||||
+ "I9LEHmN+CMPosvabQu5wkJXLuqo2TxRnAznsA8R3pCLkdPGoWMCiWRAsCn979TdY"
|
||||
+ "QbqO2qvBAD2Q19GtY7lIu6C35/enQWzJUMQE3WW0OvjLzZ0l/9mA2FBRR+3F9A1d"
|
||||
+ "rBdnmv0c3TcCgYEAi2i+ggVZcqPbtgrLOk5WVGo9F1GqUBvlgNn30WWNTx4zIaEk"
|
||||
+ "HSxtyaOLTxtq2odV7Kr3LGiKxwPpn/T+Ief+oIp92YcTn+VfJVGw4Z3BezqbR8lA"
|
||||
+ "Uf/+HF5ZfpMrVXtZD4Igs3I33Duv4sCuqhEvLWTc44pHifVloozNxYfRfU0CgYBN"
|
||||
+ "HXa7a6cJ1Yp829l62QlJKtx6Ymj95oAnQu5Ez2ROiZMqXRO4nucOjGUP55Orac1a"
|
||||
+ "FiGm+mC/skFS0MWgW8evaHGDbWU180wheQ35hW6oKAb7myRHtr4q20ouEtQMdQIF"
|
||||
+ "snV39G1iyqeeAsf7dxWElydXpRi2b68i3BIgzhzebQKBgQCdUQuTsqV9y/JFpu6H"
|
||||
+ "c5TVvhG/ubfBspI5DhQqIGijnVBzFT//UfIYMSKJo75qqBEyP2EJSmCsunWsAFsM"
|
||||
+ "TszuiGTkrKcZy9G0wJqPztZZl2F2+bJgnA6nBEV7g5PA4Af+QSmaIhRwqGDAuROR"
|
||||
+ "47jndeyIaMTNETEmOnms+as17g==";
|
||||
// @formatter:on
|
||||
|
||||
public static final RSAPrivateKey DEFAULT_PRIVATE_KEY;
|
||||
static {
|
||||
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(DEFAULT_RSA_PRIVATE_KEY));
|
||||
try {
|
||||
DEFAULT_PRIVATE_KEY = (RSAPrivateKey) kf.generatePrivate(spec);
|
||||
}
|
||||
catch (InvalidKeySpecException ex) {
|
||||
throw new IllegalArgumentException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static final KeyPair DEFAULT_RSA_KEY_PAIR = new KeyPair(DEFAULT_PUBLIC_KEY, DEFAULT_PRIVATE_KEY);
|
||||
|
||||
public static final KeyPair DEFAULT_EC_KEY_PAIR = generateEcKeyPair();
|
||||
|
||||
static KeyPair generateEcKeyPair() {
|
||||
EllipticCurve ellipticCurve = new EllipticCurve(
|
||||
new ECFieldFp(new BigInteger(
|
||||
"115792089210356248762697446949407573530086143415290314195533631308867097853951")),
|
||||
new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"),
|
||||
new BigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291"));
|
||||
ECPoint ecPoint = new ECPoint(
|
||||
new BigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"),
|
||||
new BigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109"));
|
||||
ECParameterSpec ecParameterSpec = new ECParameterSpec(ellipticCurve, ecPoint,
|
||||
new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"), 1);
|
||||
|
||||
KeyPair keyPair;
|
||||
try {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
|
||||
keyPairGenerator.initialize(ecParameterSpec);
|
||||
keyPair = keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
private TestKeys() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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 sample.jpa;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import org.assertj.core.api.ObjectAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import sample.AuthorizationCodeGrantFlow;
|
||||
import sample.jose.TestJwks;
|
||||
import sample.jpa.service.authorization.JpaOAuth2AuthorizationService;
|
||||
import sample.jpa.service.authorizationConsent.JpaOAuth2AuthorizationConsentService;
|
||||
import sample.jpa.service.client.JpaRegisteredClientRepository;
|
||||
import sample.test.SpringTestContext;
|
||||
import sample.test.SpringTestContextExtension;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
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.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static sample.util.RegisteredClients.messagingClient;
|
||||
|
||||
/**
|
||||
* Tests for the guide How-to: Implement core services with JPA.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class JpaTests {
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private RegisteredClientRepository registeredClientRepository;
|
||||
|
||||
@Autowired
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
|
||||
@Autowired
|
||||
private OAuth2AuthorizationConsentService authorizationConsentService;
|
||||
|
||||
@Test
|
||||
public void oidcLoginWhenJpaCoreServicesAutowiredThenUsed() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfig.class).autowire();
|
||||
assertThat(this.registeredClientRepository).isInstanceOf(JpaRegisteredClientRepository.class);
|
||||
assertThat(this.authorizationService).isInstanceOf(JpaOAuth2AuthorizationService.class);
|
||||
assertThat(this.authorizationConsentService).isInstanceOf(JpaOAuth2AuthorizationConsentService.class);
|
||||
|
||||
RegisteredClient registeredClient = messagingClient();
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
AuthorizationCodeGrantFlow authorizationCodeGrantFlow = new AuthorizationCodeGrantFlow(this.mockMvc);
|
||||
authorizationCodeGrantFlow.setUsername("user");
|
||||
authorizationCodeGrantFlow.addScope("message.read");
|
||||
authorizationCodeGrantFlow.addScope("message.write");
|
||||
|
||||
String state = authorizationCodeGrantFlow.authorize(registeredClient);
|
||||
assertThatAuthorization(state, OAuth2ParameterNames.STATE).isNotNull();
|
||||
assertThatAuthorization(state, null).isNotNull();
|
||||
|
||||
String authorizationCode = authorizationCodeGrantFlow.submitConsent(registeredClient, state);
|
||||
assertThatAuthorization(authorizationCode, OAuth2ParameterNames.CODE).isNotNull();
|
||||
assertThatAuthorization(authorizationCode, null).isNotNull();
|
||||
|
||||
Map<String, Object> tokenResponse = authorizationCodeGrantFlow.getTokenResponse(registeredClient, authorizationCode);
|
||||
String accessToken = (String) tokenResponse.get(OAuth2ParameterNames.ACCESS_TOKEN);
|
||||
assertThatAuthorization(accessToken, OAuth2ParameterNames.ACCESS_TOKEN).isNotNull();
|
||||
assertThatAuthorization(accessToken, null).isNotNull();
|
||||
|
||||
String refreshToken = (String) tokenResponse.get(OAuth2ParameterNames.REFRESH_TOKEN);
|
||||
assertThatAuthorization(refreshToken, OAuth2ParameterNames.REFRESH_TOKEN).isNotNull();
|
||||
assertThatAuthorization(refreshToken, null).isNotNull();
|
||||
|
||||
String idToken = (String) tokenResponse.get(OidcParameterNames.ID_TOKEN);
|
||||
assertThatAuthorization(idToken, OidcParameterNames.ID_TOKEN).isNull(); // id_token is not searchable
|
||||
|
||||
OAuth2Authorization authorization = findAuthorization(accessToken, OAuth2ParameterNames.ACCESS_TOKEN);
|
||||
assertThat(authorization.getToken(idToken)).isNotNull();
|
||||
|
||||
String scopes = (String) tokenResponse.get(OAuth2ParameterNames.SCOPE);
|
||||
OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService.findById(
|
||||
registeredClient.getId(), "user");
|
||||
assertThat(authorizationConsent).isNotNull();
|
||||
assertThat(authorizationConsent.getScopes()).containsExactlyInAnyOrder(
|
||||
StringUtils.delimitedListToStringArray(scopes, " "));
|
||||
}
|
||||
|
||||
private ObjectAssert<OAuth2Authorization> assertThatAuthorization(String token, String tokenType) {
|
||||
return assertThat(findAuthorization(token, tokenType));
|
||||
}
|
||||
|
||||
private OAuth2Authorization findAuthorization(String token, String tokenType) {
|
||||
return this.authorizationService.findByToken(token, tokenType == null ? null : new OAuth2TokenType(tokenType));
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@EnableAutoConfiguration
|
||||
@ComponentScan
|
||||
static class AuthorizationServerConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
|
||||
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
|
||||
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
|
||||
|
||||
// @formatter:off
|
||||
http
|
||||
.exceptionHandling(exceptions ->
|
||||
exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
|
||||
)
|
||||
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JWKSource<SecurityContext> jwkSource() {
|
||||
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
|
||||
return new ImmutableJWKSet<>(jwkSet);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
|
||||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder().build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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 sample.test;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
|
||||
import org.springframework.mock.web.MockServletConfig;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.config.BeanIds;
|
||||
import org.springframework.test.context.web.GenericXmlWebContextLoader;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.RequestPostProcessor;
|
||||
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
|
||||
import org.springframework.web.context.ConfigurableWebApplicationContext;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.context.support.XmlWebApplicationContext;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class SpringTestContext implements Closeable {
|
||||
|
||||
private Object test;
|
||||
|
||||
private ConfigurableWebApplicationContext context;
|
||||
|
||||
private List<Filter> filters = new ArrayList<>();
|
||||
|
||||
public SpringTestContext(Object test) {
|
||||
setTest(test);
|
||||
}
|
||||
|
||||
public void setTest(Object test) {
|
||||
this.test = test;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
this.context.close();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
}
|
||||
}
|
||||
|
||||
public SpringTestContext context(ConfigurableWebApplicationContext context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpringTestContext register(Class<?>... classes) {
|
||||
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
|
||||
applicationContext.register(classes);
|
||||
this.context = applicationContext;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpringTestContext testConfigLocations(String... configLocations) {
|
||||
GenericXmlWebContextLoader loader = new GenericXmlWebContextLoader();
|
||||
String[] locations = loader.processLocations(this.test.getClass(), configLocations);
|
||||
return configLocations(locations);
|
||||
}
|
||||
|
||||
public SpringTestContext configLocations(String... configLocations) {
|
||||
XmlWebApplicationContext context = new XmlWebApplicationContext();
|
||||
context.setConfigLocations(configLocations);
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpringTestContext mockMvcAfterSpringSecurityOk() {
|
||||
return addFilter(new OncePerRequestFilter() {
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
|
||||
FilterChain filterChain) {
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private SpringTestContext addFilter(Filter filter) {
|
||||
this.filters.add(filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigurableWebApplicationContext getContext() {
|
||||
if (!this.context.isRunning()) {
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.setServletConfig(new MockServletConfig());
|
||||
this.context.refresh();
|
||||
}
|
||||
return this.context;
|
||||
}
|
||||
|
||||
public void autowire() {
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.setServletConfig(new MockServletConfig());
|
||||
this.context.refresh();
|
||||
if (this.context.containsBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN)) {
|
||||
// @formatter:off
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).
|
||||
apply(springSecurity())
|
||||
.apply(new AddFilter())
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.context.getBeanFactory().registerResolvableDependency(MockMvc.class, mockMvc);
|
||||
}
|
||||
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
|
||||
bpp.setBeanFactory(this.context.getBeanFactory());
|
||||
bpp.processInjection(this.test);
|
||||
}
|
||||
|
||||
private class AddFilter implements MockMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder<?> builder,
|
||||
WebApplicationContext context) {
|
||||
builder.addFilters(SpringTestContext.this.filters.toArray(new Filter[0]));
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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 sample.test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
|
||||
import org.springframework.security.test.context.TestSecurityContextHolder;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class SpringTestContextExtension implements BeforeEachCallback, AfterEachCallback {
|
||||
|
||||
@Override
|
||||
public void afterEach(ExtensionContext context) throws Exception {
|
||||
TestSecurityContextHolder.clearContext();
|
||||
getContexts(context.getRequiredTestInstance()).forEach((springTestContext) -> springTestContext.close());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeEach(ExtensionContext context) throws Exception {
|
||||
Object testInstance = context.getRequiredTestInstance();
|
||||
getContexts(testInstance).forEach((springTestContext) -> springTestContext.setTest(testInstance));
|
||||
}
|
||||
|
||||
private static List<SpringTestContext> getContexts(Object test) throws IllegalAccessException {
|
||||
Field[] declaredFields = test.getClass().getDeclaredFields();
|
||||
List<SpringTestContext> result = new ArrayList<>();
|
||||
for (Field field : declaredFields) {
|
||||
if (SpringTestContext.class.isAssignableFrom(field.getType())) {
|
||||
result.add((SpringTestContext) field.get(test));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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 sample.userinfo;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import sample.AuthorizationCodeGrantFlow;
|
||||
import sample.test.SpringTestContext;
|
||||
import sample.test.SpringTestContextExtension;
|
||||
import sample.userinfo.idtoken.IdTokenCustomizerConfig;
|
||||
import sample.userinfo.idtoken.OidcUserInfoService;
|
||||
import sample.userinfo.jwt.JwtTokenCustomizerConfig;
|
||||
import sample.userinfo.jwt.JwtUserInfoMapperSecurityConfig;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Tests for the guide How-to: Customize the OpenID Connect 1.0 UserInfo response.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class EnableUserInfoSecurityConfigTests {
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private RegisteredClientRepository registeredClientRepository;
|
||||
|
||||
@Test
|
||||
public void userInfoWhenEnabledThenSuccess() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfig.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId("messaging-client");
|
||||
assertThat(registeredClient).isNotNull();
|
||||
|
||||
AuthorizationCodeGrantFlow authorizationCodeGrantFlow = new AuthorizationCodeGrantFlow(this.mockMvc);
|
||||
authorizationCodeGrantFlow.setUsername("user1");
|
||||
authorizationCodeGrantFlow.addScope("message.read");
|
||||
authorizationCodeGrantFlow.addScope("message.write");
|
||||
|
||||
String state = authorizationCodeGrantFlow.authorize(registeredClient);
|
||||
String authorizationCode = authorizationCodeGrantFlow.submitConsent(registeredClient, state);
|
||||
Map<String, Object> tokenResponse = authorizationCodeGrantFlow.getTokenResponse(registeredClient, authorizationCode);
|
||||
String accessToken = (String) tokenResponse.get(OAuth2ParameterNames.ACCESS_TOKEN);
|
||||
|
||||
this.mockMvc.perform(get("/userinfo")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON_VALUE)))
|
||||
.andExpect(jsonPath("sub").value("user1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userInfoWhenIdTokenCustomizerThenIdTokenClaimsMappedToResponse() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigWithIdTokenCustomizer.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId("messaging-client");
|
||||
assertThat(registeredClient).isNotNull();
|
||||
|
||||
AuthorizationCodeGrantFlow authorizationCodeGrantFlow = new AuthorizationCodeGrantFlow(this.mockMvc);
|
||||
authorizationCodeGrantFlow.setUsername("user1");
|
||||
authorizationCodeGrantFlow.addScope(OidcScopes.ADDRESS);
|
||||
authorizationCodeGrantFlow.addScope(OidcScopes.EMAIL);
|
||||
authorizationCodeGrantFlow.addScope(OidcScopes.PHONE);
|
||||
authorizationCodeGrantFlow.addScope(OidcScopes.PROFILE);
|
||||
|
||||
String state = authorizationCodeGrantFlow.authorize(registeredClient);
|
||||
String authorizationCode = authorizationCodeGrantFlow.submitConsent(registeredClient, state);
|
||||
Map<String, Object> tokenResponse = authorizationCodeGrantFlow.getTokenResponse(registeredClient, authorizationCode);
|
||||
String accessToken = (String) tokenResponse.get(OAuth2ParameterNames.ACCESS_TOKEN);
|
||||
|
||||
this.mockMvc.perform(get("/userinfo")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON_VALUE)))
|
||||
.andExpectAll(
|
||||
jsonPath("sub").value("user1"),
|
||||
jsonPath("name").value("First Last"),
|
||||
jsonPath("given_name").value("First"),
|
||||
jsonPath("family_name").value("Last"),
|
||||
jsonPath("middle_name").value("Middle"),
|
||||
jsonPath("nickname").value("User"),
|
||||
jsonPath("preferred_username").value("user1"),
|
||||
jsonPath("profile").value("https://example.com/user1"),
|
||||
jsonPath("picture").value("https://example.com/user1.jpg"),
|
||||
jsonPath("website").value("https://example.com"),
|
||||
jsonPath("email").value("user1@example.com"),
|
||||
jsonPath("email_verified").value("true"),
|
||||
jsonPath("gender").value("female"),
|
||||
jsonPath("birthdate").value("1970-01-01"),
|
||||
jsonPath("zoneinfo").value("Europe/Paris"),
|
||||
jsonPath("locale").value("en-US"),
|
||||
jsonPath("phone_number").value("+1 (604) 555-1234;ext=5678"),
|
||||
jsonPath("address.formatted").value("Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"),
|
||||
jsonPath("updated_at").value("1970-01-01T00:00:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userInfoWhenUserInfoMapperThenClaimsMappedToResponse() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigWithJwtTokenCustomizer.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId("messaging-client");
|
||||
assertThat(registeredClient).isNotNull();
|
||||
|
||||
AuthorizationCodeGrantFlow authorizationCodeGrantFlow = new AuthorizationCodeGrantFlow(this.mockMvc);
|
||||
authorizationCodeGrantFlow.setUsername("user1");
|
||||
authorizationCodeGrantFlow.addScope("message.read");
|
||||
authorizationCodeGrantFlow.addScope("message.write");
|
||||
|
||||
String state = authorizationCodeGrantFlow.authorize(registeredClient);
|
||||
String authorizationCode = authorizationCodeGrantFlow.submitConsent(registeredClient, state);
|
||||
Map<String, Object> tokenResponse = authorizationCodeGrantFlow.getTokenResponse(registeredClient, authorizationCode);
|
||||
String accessToken = (String) tokenResponse.get(OAuth2ParameterNames.ACCESS_TOKEN);
|
||||
|
||||
this.mockMvc.perform(get("/userinfo")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON_VALUE)))
|
||||
.andExpectAll(
|
||||
jsonPath("sub").value("user1"),
|
||||
jsonPath("claim-1").value("value-1"),
|
||||
jsonPath("claim-2").value("value-2")
|
||||
);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@EnableAutoConfiguration
|
||||
@Import(EnableUserInfoSecurityConfig.class)
|
||||
static class AuthorizationServerConfig {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import({EnableUserInfoSecurityConfig.class, IdTokenCustomizerConfig.class})
|
||||
static class AuthorizationServerConfigWithIdTokenCustomizer {
|
||||
|
||||
@Bean
|
||||
public OidcUserInfoService userInfoService() {
|
||||
return new OidcUserInfoService();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@EnableAutoConfiguration
|
||||
@Import({JwtUserInfoMapperSecurityConfig.class, JwtTokenCustomizerConfig.class})
|
||||
static class AuthorizationServerConfigWithJwtTokenCustomizer {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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 sample.util;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class RegisteredClients {
|
||||
// @formatter:off
|
||||
public static RegisteredClient messagingClient() {
|
||||
return RegisteredClient.withId(UUID.randomUUID().toString())
|
||||
.clientId("messaging-client")
|
||||
.clientSecret("{noop}secret")
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||
.redirectUri("http://127.0.0.1:8080/authorized")
|
||||
.scope(OidcScopes.OPENID)
|
||||
.scope("message.read")
|
||||
.scope("message.write")
|
||||
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
|
||||
.build();
|
||||
}
|
||||
// @formatter:on
|
||||
}
|
||||
23
docs/src/docs/asciidoc/getting-help.adoc
Normal file
23
docs/src/docs/asciidoc/getting-help.adoc
Normal file
@@ -0,0 +1,23 @@
|
||||
[[getting-help]]
|
||||
= Getting Help
|
||||
|
||||
[[community]]
|
||||
== Community
|
||||
|
||||
Welcome to the https://docs.spring.io/spring-security/reference/community.html[Spring Security Community].
|
||||
Spring Authorization Server is an open source project led by the Spring Security team.
|
||||
If you need help with Spring Authorization Server, we are here to help.
|
||||
|
||||
[[resources]]
|
||||
== Resources
|
||||
|
||||
The following are some of the best ways to get help:
|
||||
|
||||
* Try the xref:how-to.adoc[How-to guides]. They provide solutions to the most common questions.
|
||||
* Learn the Spring Security basics that Spring Authorization Server builds on. If you are starting out with Spring Security, check the https://spring.io/projects/spring-security#learn[reference documentation] or try one of the https://github.com/spring-projects/spring-security-samples[samples].
|
||||
* Read through xref:index.adoc[this documentation].
|
||||
* Try one of our many https://github.com/spring-projects/spring-authorization-server/tree/main/samples[sample applications].
|
||||
* Ask a question on Stack Overflow with the https://stackoverflow.com/questions/tagged/spring-security[`spring-security`] tag.
|
||||
* Report bugs and enhancement requests on https://github.com/spring-projects/spring-authorization-server/issues[GitHub].
|
||||
|
||||
NOTE: Spring Authorization Server is open source, including the documentation. If you find problems with the docs or if you want to improve them, please https://github.com/spring-projects/spring-authorization-server[get involved].
|
||||
58
docs/src/docs/asciidoc/getting-started.adoc
Normal file
58
docs/src/docs/asciidoc/getting-started.adoc
Normal file
@@ -0,0 +1,58 @@
|
||||
[[getting-started]]
|
||||
= Getting Started
|
||||
|
||||
If you are just getting started with Spring Authorization Server, the following sections walk you through creating your first application.
|
||||
|
||||
[[system-requirements]]
|
||||
== System Requirements
|
||||
|
||||
Spring Authorization Server requires a Java 8 or higher Runtime Environment.
|
||||
|
||||
[[installing-spring-authorization-server]]
|
||||
== Installing Spring Authorization Server
|
||||
|
||||
Spring Authorization Server can be used anywhere you already use https://docs.spring.io/spring-security/reference/prerequisites.html[Spring Security].
|
||||
|
||||
The easiest way to begin using Spring Authorization Server is by creating a https://spring.io/projects/spring-boot[Spring Boot]-based application.
|
||||
You can use https://start.spring.io[start.spring.io] to generate a basic project or use the https://github.com/spring-projects/spring-authorization-server/tree/main/samples/default-authorizationserver[default authorization server sample] as a guide.
|
||||
Then add Spring Authorization Server as a dependency, as in the following example:
|
||||
|
||||
[[maven-dependency]]
|
||||
.Maven
|
||||
[source,xml,role="primary",subs="attributes,verbatim"]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-authorization-server</artifactId>
|
||||
<version>{spring-authorization-server-version}</version>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
[[gradle-dependency]]
|
||||
.Gradle
|
||||
[source,gradle,role="secondary",subs="attributes,verbatim"]
|
||||
----
|
||||
implementation "org.springframework.security:spring-security-oauth2-authorization-server:{spring-authorization-server-version}"
|
||||
----
|
||||
|
||||
TIP: See https://docs.spring.io/spring-boot/docs/current/reference/html/getting-started.html#getting-started.installing[Installing Spring Boot] for more information on using Spring Boot with Maven or Gradle.
|
||||
|
||||
[[developing-your-first-application]]
|
||||
== Developing Your First Application
|
||||
|
||||
To get started, you need the minimum required components defined as a `@Bean` in a Spring `@Configuration`. These components can be defined as follows:
|
||||
|
||||
TIP: To skip the setup and run a working example, see the https://github.com/spring-projects/spring-authorization-server/tree/main/samples/default-authorizationserver[default authorization server sample].
|
||||
|
||||
[[sample.gettingStarted]]
|
||||
include::code:SecurityConfig[]
|
||||
|
||||
This is a minimal configuration for getting started quickly. To understand what each component is used for, see the following descriptions:
|
||||
|
||||
<1> A Spring Security filter chain for the xref:protocol-endpoints.adoc[Protocol Endpoints].
|
||||
<2> A Spring Security filter chain for https://docs.spring.io/spring-security/reference/servlet/authentication/index.html[authentication].
|
||||
<3> An instance of {spring-security-api-base-url}/org/springframework/security/core/userdetails/UserDetailsService.html[`UserDetailsService`] for retrieving users to authenticate.
|
||||
<4> An instance of xref:core-model-components.adoc#registered-client-repository[`RegisteredClientRepository`] for managing clients.
|
||||
<5> An instance of `com.nimbusds.jose.jwk.source.JWKSource` for signing access tokens.
|
||||
<6> An instance of `java.security.KeyPair` with keys generated on startup used to create the `JWKSource` above.
|
||||
<7> An instance of xref:configuration-model#configuring-authorization-server-settings[`AuthorizationServerSettings`] to configure Spring Authorization Server.
|
||||
233
docs/src/docs/asciidoc/guides/how-to-jpa.adoc
Normal file
233
docs/src/docs/asciidoc/guides/how-to-jpa.adoc
Normal file
@@ -0,0 +1,233 @@
|
||||
[[how-to-jpa]]
|
||||
= How-to: Implement core services with JPA
|
||||
:index-link: ../how-to.html
|
||||
:docs-dir: ..
|
||||
|
||||
This guide shows how to implement the xref:{docs-dir}/core-model-components.adoc#core-model-components[core services] of xref:{docs-dir}/index.adoc#top[Spring Authorization Server] with JPA.
|
||||
The purpose of this guide is to provide a starting point for implementing these services yourself, with the intention that you can make modifications to suit your needs.
|
||||
|
||||
* <<define-data-model>>
|
||||
* <<create-jpa-entities>>
|
||||
* <<create-spring-data-repositories>>
|
||||
* <<implement-core-services>>
|
||||
|
||||
[[define-data-model]]
|
||||
== Define the data model
|
||||
|
||||
This guide provides a starting point for the data model and uses the simplest possible structure and data types.
|
||||
To come up with the initial schema, we begin by reviewing the xref:{docs-dir}/core-model-components.adoc#core-model-components[domain objects] used by the core services.
|
||||
|
||||
[NOTE]
|
||||
Except for token, state, metadata, settings, and claims values, we use the JPA default column length of 255 for all columns.
|
||||
In reality, the length and even type of columns you use may need to be customized.
|
||||
You are encouraged to experiment and test before deploying to production.
|
||||
|
||||
* <<client-schema>>
|
||||
* <<authorization-schema>>
|
||||
* <<authorization-consent-schema>>
|
||||
|
||||
[[client-schema]]
|
||||
=== Client Schema
|
||||
|
||||
The xref:{docs-dir}/core-model-components.adoc#registered-client[`RegisteredClient`] domain object contains a few multi-valued fields and some settings fields that require storing arbitrary key/value data.
|
||||
The following listing shows the `client` schema.
|
||||
|
||||
.Client Schema
|
||||
[source,sql]
|
||||
----
|
||||
CREATE TABLE client (
|
||||
id varchar(255) NOT NULL,
|
||||
clientId varchar(255) NOT NULL,
|
||||
clientIdIssuedAt timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
clientSecret varchar(255) DEFAULT NULL,
|
||||
clientSecretExpiresAt timestamp DEFAULT NULL,
|
||||
clientName varchar(255) NOT NULL,
|
||||
clientAuthenticationMethods varchar(1000) NOT NULL,
|
||||
authorizationGrantTypes varchar(1000) NOT NULL,
|
||||
redirectUris varchar(1000) DEFAULT NULL,
|
||||
scopes varchar(1000) NOT NULL,
|
||||
clientSettings varchar(2000) NOT NULL,
|
||||
tokenSettings varchar(2000) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
----
|
||||
|
||||
[[authorization-schema]]
|
||||
=== Authorization Schema
|
||||
|
||||
The xref:{docs-dir}/core-model-components.adoc#oauth2-authorization[`OAuth2Authorization`] domain object is more complex and contains several multi-valued fields as well as numerous arbitrarily long token values, metadata, settings and claims values.
|
||||
The built-in JDBC implementation utilizes a flattened structure that prefers performance over normalization, which we adopt here as well.
|
||||
|
||||
[CAUTION]
|
||||
It has been difficult to find a flattened database schema that works well in all cases and with all database vendors.
|
||||
You may need to normalize or heavily alter the following schema for your needs.
|
||||
|
||||
The following listing shows the `authorization` schema.
|
||||
|
||||
.Authorization Schema
|
||||
[source,sql]
|
||||
----
|
||||
CREATE TABLE authorization (
|
||||
id varchar(255) NOT NULL,
|
||||
registeredClientId varchar(255) NOT NULL,
|
||||
principalName varchar(255) NOT NULL,
|
||||
authorizationGrantType varchar(255) NOT NULL,
|
||||
attributes varchar(4000) DEFAULT NULL,
|
||||
state varchar(500) DEFAULT NULL,
|
||||
authorizationCodeValue varchar(4000) DEFAULT NULL,
|
||||
authorizationCodeIssuedAt timestamp DEFAULT NULL,
|
||||
authorizationCodeExpiresAt timestamp DEFAULT NULL,
|
||||
authorizationCodeMetadata varchar(2000) DEFAULT NULL,
|
||||
accessTokenValue varchar(4000) DEFAULT NULL,
|
||||
accessTokenIssuedAt timestamp DEFAULT NULL,
|
||||
accessTokenExpiresAt timestamp DEFAULT NULL,
|
||||
accessTokenMetadata varchar(2000) DEFAULT NULL,
|
||||
accessTokenType varchar(255) DEFAULT NULL,
|
||||
accessTokenScopes varchar(1000) DEFAULT NULL,
|
||||
refreshTokenValue varchar(4000) DEFAULT NULL,
|
||||
refreshTokenIssuedAt timestamp DEFAULT NULL,
|
||||
refreshTokenExpiresAt timestamp DEFAULT NULL,
|
||||
refreshTokenMetadata varchar(2000) DEFAULT NULL,
|
||||
oidcIdTokenValue varchar(4000) DEFAULT NULL,
|
||||
oidcIdTokenIssuedAt timestamp DEFAULT NULL,
|
||||
oidcIdTokenExpiresAt timestamp DEFAULT NULL,
|
||||
oidcIdTokenMetadata varchar(2000) DEFAULT NULL,
|
||||
oidcIdTokenClaims varchar(2000) DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
----
|
||||
|
||||
[[authorization-consent-schema]]
|
||||
=== Authorization Consent Schema
|
||||
|
||||
The xref:{docs-dir}/core-model-components.adoc#oauth2-authorization-consent[`OAuth2AuthorizationConsent`] domain object is the simplest to model and contains only a single multi-valued field in addition to a composite key.
|
||||
The following listing shows the `authorizationConsent` schema.
|
||||
|
||||
.Authorization Consent Schema
|
||||
[source,sql]
|
||||
----
|
||||
CREATE TABLE authorizationConsent (
|
||||
registeredClientId varchar(255) NOT NULL,
|
||||
principalName varchar(255) NOT NULL,
|
||||
authorities varchar(1000) NOT NULL,
|
||||
PRIMARY KEY (registeredClientId, principalName)
|
||||
);
|
||||
----
|
||||
|
||||
[[create-jpa-entities]]
|
||||
== Create JPA entities
|
||||
|
||||
The preceding schema examples provide a reference for the structure of the entities we need to create.
|
||||
|
||||
[NOTE]
|
||||
The following entities are minimally annotated and are just examples.
|
||||
They allow the schema to be created dynamically and therefore do not require the above sql scripts to be executed manually.
|
||||
|
||||
* <<client-entity>>
|
||||
* <<authorization-entity>>
|
||||
* <<authorization-consent-entity>>
|
||||
|
||||
[[client-entity]]
|
||||
=== Client Entity
|
||||
|
||||
The following listing shows the `Client` entity, which is used to persist information mapped from the xref:{docs-dir}/core-model-components.adoc#registered-client[`RegisteredClient`] domain object.
|
||||
|
||||
[[sample.jpa.entity.client]]
|
||||
.Client Entity
|
||||
include::code:Client[]
|
||||
|
||||
[[authorization-entity]]
|
||||
=== Authorization Entity
|
||||
|
||||
The following listing shows the `Authorization` entity, which is used to persist information mapped from the xref:{docs-dir}/core-model-components.adoc#oauth2-authorization[`OAuth2Authorization`] domain object.
|
||||
|
||||
[[sample.jpa.entity.authorization]]
|
||||
.Authorization Entity
|
||||
include::code:Authorization[]
|
||||
|
||||
[[authorization-consent-entity]]
|
||||
=== Authorization Consent Entity
|
||||
|
||||
The following listing shows the `AuthorizationConsent` entity, which is used to persist information mapped from the xref:{docs-dir}/core-model-components.adoc#oauth2-authorization-consent[`OAuth2AuthorizationConsent`] domain object.
|
||||
|
||||
[[sample.jpa.entity.authorizationConsent]]
|
||||
.Authorization Consent Entity
|
||||
include::code:AuthorizationConsent[]
|
||||
|
||||
[[create-spring-data-repositories]]
|
||||
== Create Spring Data repositories
|
||||
|
||||
By closely examining the interfaces of each core service and reviewing the `Jdbc` implementations, we can derive a minimal set of queries needed for supporting a JPA version of each interface.
|
||||
|
||||
* <<client-repository>>
|
||||
* <<authorization-repository>>
|
||||
* <<authorization-consent-repository>>
|
||||
|
||||
[[client-repository]]
|
||||
=== Client Repository
|
||||
|
||||
The following listing shows the `ClientRepository`, which is able to find a <<client-entity,`Client`>> by the `id` and `clientId` fields.
|
||||
|
||||
[[sample.jpa.repository.client]]
|
||||
.Client Repository
|
||||
include::code:ClientRepository[]
|
||||
|
||||
[[authorization-repository]]
|
||||
=== Authorization Repository
|
||||
|
||||
The following listing shows the `AuthorizationRepository`, which is able to find an <<authorization-entity,`Authorization`>> by the `id` field as well as the `state`, `authorizationCodeValue`, `accessTokenValue` and `refreshTokenValue` token fields.
|
||||
It also allows querying a combination of token fields.
|
||||
|
||||
[[sample.jpa.repository.authorization]]
|
||||
.Authorization Repository
|
||||
include::code:AuthorizationRepository[]
|
||||
|
||||
[[authorization-consent-repository]]
|
||||
=== Authorization Consent Repository
|
||||
|
||||
The following listing shows the `AuthorizationConsentRepository`, which is able to find and delete an <<authorization-consent-entity,`AuthorizationConsent`>> by the `registeredClientId` and `principalName` fields that form a composite primary key.
|
||||
|
||||
[[sample.jpa.repository.authorizationConsent]]
|
||||
.Authorization Consent Repository
|
||||
include::code:AuthorizationConsentRepository[]
|
||||
|
||||
[[implement-core-services]]
|
||||
== Implement core services
|
||||
|
||||
With the above <<create-jpa-entities,entities>> and <<create-spring-data-repositories,repositories>>, we can begin implementing the core services.
|
||||
By reviewing the `Jdbc` implementations, we can derive a minimal set of internal utilities for converting to and from string values for enumerations and reading and writing JSON data for attributes, settings, metadata and claims fields.
|
||||
|
||||
[CAUTION]
|
||||
Keep in mind that writing JSON data to text columns with a fixed length has proven problematic with the `Jdbc` implementations.
|
||||
While these examples continue to do so, you may need to split these fields out into a separate table or data store that supports arbitrarily long data values.
|
||||
|
||||
* <<registered-client-repository>>
|
||||
* <<authorization-service>>
|
||||
* <<authorization-consent-service>>
|
||||
|
||||
[[registered-client-repository]]
|
||||
=== Registered Client Repository
|
||||
|
||||
The following listing shows the `JpaRegisteredClientRepository`, which uses a <<client-repository,`ClientRepository`>> for persisting a <<client-entity,`Client`>> and maps to and from the xref:{docs-dir}/core-model-components.adoc#registered-client[`RegisteredClient`] domain object.
|
||||
|
||||
[[sample.jpa.service.client]]
|
||||
.`RegisteredClientRepository` Implementation
|
||||
include::code:JpaRegisteredClientRepository[]
|
||||
|
||||
[[authorization-service]]
|
||||
=== Authorization Service
|
||||
|
||||
The following listing shows the `JpaOAuth2AuthorizationService`, which uses an <<authorization-repository,`AuthorizationRepository`>> for persisting an <<authorization-entity,`Authorization`>> and maps to and from the xref:{docs-dir}/core-model-components.adoc#oauth2-authorization[`OAuth2Authorization`] domain object.
|
||||
|
||||
[[sample.jpa.service.authorization]]
|
||||
.`OAuth2AuthorizationService` Implementation
|
||||
include::code:JpaOAuth2AuthorizationService[]
|
||||
|
||||
[[authorization-consent-service]]
|
||||
=== Authorization Consent Service
|
||||
|
||||
The following listing shows the `JpaOAuth2AuthorizationConsentService`, which uses an <<authorization-consent-repository,`AuthorizationConsentRepository`>> for persisting an <<authorization-consent-entity,`AuthorizationConsent`>> and maps to and from the xref:{docs-dir}/core-model-components.adoc#oauth2-authorization-consent[`OAuth2AuthorizationConsent`] domain object.
|
||||
|
||||
[[sample.jpa.service.authorizationConsent]]
|
||||
.`OAuth2AuthorizationConsentService` Implementation
|
||||
include::code:JpaOAuth2AuthorizationConsentService[]
|
||||
86
docs/src/docs/asciidoc/guides/how-to-userinfo.adoc
Normal file
86
docs/src/docs/asciidoc/guides/how-to-userinfo.adoc
Normal file
@@ -0,0 +1,86 @@
|
||||
[[how-to-userinfo]]
|
||||
= How-to: Customize the OpenID Connect 1.0 UserInfo response
|
||||
:index-link: ../how-to.html
|
||||
:docs-dir: ..
|
||||
|
||||
This guide shows how to customize the xref:{docs-dir}/protocol-endpoints.adoc#oidc-user-info-endpoint[UserInfo endpoint] of the xref:{docs-dir}/index.adoc#top[Spring Authorization Server].
|
||||
The purpose of this guide is to demonstrate how to enable the endpoint and use the available customization options to produce a custom response.
|
||||
|
||||
* <<enable-user-info>>
|
||||
* <<customize-user-info>>
|
||||
|
||||
[[enable-user-info]]
|
||||
== Enable the User Info Endpoint
|
||||
|
||||
The xref:{docs-dir}/protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint] is an OAuth2 protected resource, which *REQUIRES* an access token to be sent as a bearer token in the https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest[UserInfo request].
|
||||
|
||||
> The Access Token obtained from an OpenID Connect Authentication Request MUST be sent as a Bearer Token, per Section 2 of https://openid.net/specs/openid-connect-core-1_0.html#RFC6750[OAuth 2.0 Bearer Token Usage] [RFC6750].
|
||||
|
||||
Before customizing the response, you need to enable the UserInfo endpoint.
|
||||
The following listing shows how to enable the {spring-security-reference-base-url}/servlet/oauth2/resource-server/jwt.html[OAuth2 resource server configuration].
|
||||
|
||||
[[sample.userinfo]]
|
||||
include::code:EnableUserInfoSecurityConfig[]
|
||||
|
||||
TIP: Click on the "Expanded folded text" icon in the code sample above to display the full example.
|
||||
|
||||
This configuration provides the following:
|
||||
|
||||
<1> A Spring Security filter chain for the xref:{docs-dir}/protocol-endpoints.adoc[Protocol Endpoints].
|
||||
<2> Resource server support that allows User Info requests to be authenticated with access tokens.
|
||||
<3> An instance of `JwtDecoder` used to validate access tokens.
|
||||
|
||||
[[customize-user-info]]
|
||||
== Customize the User Info response
|
||||
|
||||
The following sections describe some options for customizing the user info response.
|
||||
|
||||
* <<customize-id-token>>
|
||||
* <<customize-user-info-mapper>>
|
||||
|
||||
[[customize-id-token]]
|
||||
=== Customize the ID Token
|
||||
|
||||
By default, the user info response is generated by using claims from the `id_token` that are returned with the xref:{docs-dir}/protocol-endpoints.adoc#oauth2-token-endpoint[token response].
|
||||
Using the default strategy, https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims[standard claims] are returned only with the user info response based on the https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims[requested scopes] during authorization.
|
||||
|
||||
The preferred way to customize the user info response is to add standard claims to the `id_token`.
|
||||
The following listing shows how to add claims to the `id_token`.
|
||||
|
||||
[[sample.userinfo.idtoken]]
|
||||
include::code:IdTokenCustomizerConfig[]
|
||||
|
||||
This configuration provides the following:
|
||||
|
||||
<1> An instance of xref:{docs-dir}/core-model-components.adoc#oauth2-token-customizer[`OAuth2TokenCustomizer`] for customizing the `id_token`.
|
||||
<2> A custom service used to obtain user info in a domain-specific way.
|
||||
|
||||
The following listing shows a custom service for looking up user info in a domain-specific way:
|
||||
|
||||
include::code:OidcUserInfoService[]
|
||||
|
||||
[[customize-user-info-mapper]]
|
||||
=== Customize the User Info Mapper
|
||||
|
||||
To fully customize the user info response, you can provide a custom user info mapper capable of generating the object used to render the response, which is an instance of the `OidcUserInfo` class from Spring Security.
|
||||
The mapper implementation receives an instance of `OidcUserInfoAuthenticationContext` with information about the current request, including the xref:{docs-dir}/core-model-components.adoc#oauth2-authorization[`OAuth2Authorization`].
|
||||
|
||||
The following listing shows how to use the customization option that is available while working directly with the `OAuth2AuthorizationServerConfigurer`.
|
||||
|
||||
[[sample.userinfo.jwt]]
|
||||
include::code:JwtUserInfoMapperSecurityConfig[]
|
||||
|
||||
This configuration maps claims from the access token (which is a JWT when using the xref:{docs-dir}/getting-started.adoc#sample.gettingStarted[Getting Started config]) to populate the user info response and provides the following:
|
||||
|
||||
<1> A Spring Security filter chain for the xref:{docs-dir}/protocol-endpoints.adoc[Protocol Endpoints].
|
||||
<2> A user info mapper that maps claims in a domain-specific way.
|
||||
<3> An example showing the configuration option for customizing the user info mapper.
|
||||
<4> Resource server support that allows User Info requests to be authenticated with access tokens.
|
||||
<5> An example showing how to apply the `OAuth2AuthorizationServerConfigurer` to the Spring Security configuration.
|
||||
|
||||
The user info mapper is not limited to mapping claims from a JWT, but this is a simple example that demonstrates the customization option.
|
||||
Similar to the <<customize-id-token,example shown earlier>> where we customize claims of the ID token, you can customize claims of the access token itself ahead of time, as in the following example:
|
||||
|
||||
include::code:JwtTokenCustomizerConfig[]
|
||||
|
||||
Whether you customize the user info response directly or use this example and customize the access token, you can look up information in a database, perform an LDAP query, make a request to another service, or use any other means of obtaining the information you want to be presented in the user info response.
|
||||
8
docs/src/docs/asciidoc/how-to.adoc
Normal file
8
docs/src/docs/asciidoc/how-to.adoc
Normal file
@@ -0,0 +1,8 @@
|
||||
[[how-to]]
|
||||
= How-to Guides
|
||||
|
||||
[[how-to-overview]]
|
||||
== List of Guides
|
||||
|
||||
* xref:guides/how-to-userinfo.adoc[Customize the OpenID Connect 1.0 UserInfo response]
|
||||
* xref:guides/how-to-jpa.adoc[Implement core services with JPA]
|
||||
8
docs/src/docs/asciidoc/index-docinfo-footer.html
Normal file
8
docs/src/docs/asciidoc/index-docinfo-footer.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<div id="footer">
|
||||
<div id="footer-text">
|
||||
Version {spring-authorization-server-version}<br>
|
||||
Last updated {docdatetime}<br>
|
||||
Copyright © 2020-{docyear}
|
||||
<p class="paragraph">Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.</p>
|
||||
</div>
|
||||
</div>
|
||||
13
docs/src/docs/asciidoc/index-docinfo.xml
Normal file
13
docs/src/docs/asciidoc/index-docinfo.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<productname>Spring Authorization Server</productname>
|
||||
<releaseinfo>{spring-authorization-server-version}</releaseinfo>
|
||||
<copyright>
|
||||
<year>2020-{docyear}</year>
|
||||
</copyright>
|
||||
<legalnotice>
|
||||
<para>
|
||||
Copies of this document may be made for your own use and for distribution to
|
||||
others, provided that you do not charge any fee for such copies and further provided
|
||||
that each copy contains this Copyright Notice, whether distributed in print or
|
||||
electronically.
|
||||
</para>
|
||||
</legalnotice>
|
||||
15
docs/src/docs/asciidoc/index.adoc
Normal file
15
docs/src/docs/asciidoc/index.adoc
Normal file
@@ -0,0 +1,15 @@
|
||||
[[top]]
|
||||
= Spring Authorization Server Reference
|
||||
Joe Grandja, Steve Riesenberg
|
||||
v{spring-authorization-server-version}
|
||||
:docinfo: private-footer
|
||||
:nofooter:
|
||||
|
||||
[horizontal]
|
||||
xref:overview.adoc[Overview] :: Introduction and feature list
|
||||
xref:getting-help.adoc[Getting Help] :: Links to samples, questions and issues
|
||||
xref:getting-started.adoc[Getting Started] :: System requirements, dependencies and developing your first application
|
||||
xref:configuration-model.adoc[Configuration Model] :: Default configuration and customizing the configuration
|
||||
xref:core-model-components.adoc[Core Model / Components] :: Core domain model and component interfaces
|
||||
xref:protocol-endpoints.adoc[Protocol Endpoints] :: OAuth2 and OpenID Connect 1.0 protocol endpoint implementations
|
||||
xref:how-to.adoc[How-to Guides] :: Guides to get the most from Spring Authorization Server
|
||||
81
docs/src/docs/asciidoc/overview.adoc
Normal file
81
docs/src/docs/asciidoc/overview.adoc
Normal file
@@ -0,0 +1,81 @@
|
||||
[[overview]]
|
||||
= Overview
|
||||
|
||||
This site contains reference documentation and how-to guides for Spring Authorization Server.
|
||||
|
||||
[[introducing-spring-authorization-server]]
|
||||
== Introducing Spring Authorization Server
|
||||
|
||||
Spring Authorization Server is a framework that provides implementations of the https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05[OAuth 2.1] and https://openid.net/specs/openid-connect-core-1_0.html[OpenID Connect 1.0] specifications and other related specifications.
|
||||
It is built on top of https://spring.io/projects/spring-security[Spring Security] to provide a secure, light-weight, and customizable foundation for building OpenID Connect 1.0 Identity Providers and OAuth2 Authorization Server products.
|
||||
|
||||
[[feature-list]]
|
||||
== Feature List
|
||||
|
||||
Spring Authorization Server supports the following features:
|
||||
|
||||
[cols="2a,4a,6a"]
|
||||
|===
|
||||
|Category |Feature |Related specifications
|
||||
|
||||
|xref:protocol-endpoints.adoc#oauth2-token-endpoint[Authorization Grant]
|
||||
|
|
||||
* Authorization Code
|
||||
** xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[User Consent]
|
||||
* Client Credentials
|
||||
* Refresh Token
|
||||
|
|
||||
* The OAuth 2.1 Authorization Framework (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05[draft])
|
||||
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-4.1[Authorization Code Grant]
|
||||
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-4.2[Client Credentials Grant]
|
||||
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-4.3[Refresh Token Grant]
|
||||
* OpenID Connect Core 1.0 (https://openid.net/specs/openid-connect-core-1_0.html[spec])
|
||||
** https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[Authorization Code Flow]
|
||||
|
||||
|xref:core-model-components.adoc#oauth2-token-generator[Token Formats]
|
||||
|
|
||||
* Self-contained (JWT)
|
||||
* Reference (Opaque)
|
||||
|
|
||||
* JSON Web Token (JWT) (https://tools.ietf.org/html/rfc7519[RFC 7519])
|
||||
* JSON Web Signature (JWS) (https://tools.ietf.org/html/rfc7515[RFC 7515])
|
||||
|
||||
|xref:configuration-model.adoc#configuring-client-authentication[Client Authentication]
|
||||
|
|
||||
* `client_secret_basic`
|
||||
* `client_secret_post`
|
||||
* `client_secret_jwt`
|
||||
* `private_key_jwt`
|
||||
* `none` (public clients)
|
||||
|
|
||||
* The OAuth 2.1 Authorization Framework (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-2.4[Client Authentication])
|
||||
* JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication (https://tools.ietf.org/html/rfc7523[RFC 7523])
|
||||
* Proof Key for Code Exchange by OAuth Public Clients (PKCE) (https://tools.ietf.org/html/rfc7636[RFC 7636])
|
||||
|
||||
|xref:protocol-endpoints.adoc[Protocol Endpoints]
|
||||
|
|
||||
* xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[OAuth2 Authorization Endpoint]
|
||||
* xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token Endpoint]
|
||||
* xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[OAuth2 Token Introspection Endpoint]
|
||||
* xref:protocol-endpoints.adoc#oauth2-token-revocation-endpoint[OAuth2 Token Revocation Endpoint]
|
||||
* xref:protocol-endpoints.adoc#oauth2-authorization-server-metadata-endpoint[OAuth2 Authorization Server Metadata Endpoint]
|
||||
* xref:protocol-endpoints.adoc#jwk-set-endpoint[JWK Set Endpoint]
|
||||
* xref:protocol-endpoints.adoc#oidc-provider-configuration-endpoint[OpenID Connect 1.0 Provider Configuration Endpoint]
|
||||
* xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo Endpoint]
|
||||
* xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration Endpoint]
|
||||
|
|
||||
* The OAuth 2.1 Authorization Framework (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05[draft])
|
||||
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-3.1[Authorization Endpoint]
|
||||
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-3.2[Token Endpoint]
|
||||
* OAuth 2.0 Token Introspection (https://tools.ietf.org/html/rfc7662[RFC 7662])
|
||||
* OAuth 2.0 Token Revocation (https://tools.ietf.org/html/rfc7009[RFC 7009])
|
||||
* OAuth 2.0 Authorization Server Metadata (https://tools.ietf.org/html/rfc8414[RFC 8414])
|
||||
* JSON Web Key (JWK) (https://tools.ietf.org/html/rfc7517[RFC 7517])
|
||||
* OpenID Connect Discovery 1.0 (https://openid.net/specs/openid-connect-discovery-1_0.html[spec])
|
||||
** https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[Provider Configuration Endpoint]
|
||||
* OpenID Connect Core 1.0 (https://openid.net/specs/openid-connect-core-1_0.html[spec])
|
||||
** https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint]
|
||||
* OpenID Connect Dynamic Client Registration 1.0 (https://openid.net/specs/openid-connect-registration-1_0.html[spec])
|
||||
** https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration[Client Registration Endpoint]
|
||||
** https://openid.net/specs/openid-connect-registration-1_0.html#ClientConfigurationEndpoint[Client Configuration Endpoint]
|
||||
|===
|
||||
441
docs/src/docs/asciidoc/protocol-endpoints.adoc
Normal file
441
docs/src/docs/asciidoc/protocol-endpoints.adoc
Normal file
@@ -0,0 +1,441 @@
|
||||
[[protocol-endpoints]]
|
||||
= Protocol Endpoints
|
||||
|
||||
[[oauth2-authorization-endpoint]]
|
||||
== OAuth2 Authorization Endpoint
|
||||
|
||||
`OAuth2AuthorizationEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc6749#section-3.1[OAuth2 Authorization endpoint].
|
||||
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1[OAuth2 authorization requests].
|
||||
|
||||
`OAuth2AuthorizationEndpointConfigurer` provides the following configuration options:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
authorizationServerConfigurer
|
||||
.authorizationEndpoint(authorizationEndpoint ->
|
||||
authorizationEndpoint
|
||||
.authorizationRequestConverter(authorizationRequestConverter) <1>
|
||||
.authorizationRequestConverters(authorizationRequestConvertersConsumer) <2>
|
||||
.authenticationProvider(authenticationProvider) <3>
|
||||
.authenticationProviders(authenticationProvidersConsumer) <4>
|
||||
.authorizationResponseHandler(authorizationResponseHandler) <5>
|
||||
.errorResponseHandler(errorResponseHandler) <6>
|
||||
.consentPage("/oauth2/v1/authorize") <7>
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
<1> `authorizationRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1[OAuth2 authorization request] (or consent) from `HttpServletRequest` to an instance of `OAuth2AuthorizationCodeRequestAuthenticationToken` or `OAuth2AuthorizationConsentAuthenticationToken`.
|
||||
<2> `authorizationRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
|
||||
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationCodeRequestAuthenticationToken` or `OAuth2AuthorizationConsentAuthenticationToken`.
|
||||
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
|
||||
<5> `authorizationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2AuthorizationCodeRequestAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2[OAuth2AuthorizationResponse].
|
||||
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthorizationCodeRequestAuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1[OAuth2Error response].
|
||||
<7> `consentPage()`: The `URI` of the custom consent page to redirect resource owners to if consent is required during the authorization request flow.
|
||||
|
||||
`OAuth2AuthorizationEndpointConfigurer` configures the `OAuth2AuthorizationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
|
||||
`OAuth2AuthorizationEndpointFilter` is the `Filter` that processes OAuth2 authorization requests (and consents).
|
||||
|
||||
`OAuth2AuthorizationEndpointFilter` is configured with the following defaults:
|
||||
|
||||
* `*AuthenticationConverter*` -- A `DelegatingAuthenticationConverter` composed of `OAuth2AuthorizationCodeRequestAuthenticationConverter` and `OAuth2AuthorizationConsentAuthenticationConverter`.
|
||||
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2AuthorizationCodeRequestAuthenticationProvider` and `OAuth2AuthorizationConsentAuthenticationProvider`.
|
||||
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2AuthorizationCodeRequestAuthenticationToken` and returns the `OAuth2AuthorizationResponse`.
|
||||
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthorizationCodeRequestAuthenticationException` and returns the `OAuth2Error` response.
|
||||
|
||||
[[oauth2-token-endpoint]]
|
||||
== OAuth2 Token Endpoint
|
||||
|
||||
`OAuth2TokenEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc6749#section-3.2[OAuth2 Token endpoint].
|
||||
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3[OAuth2 access token requests].
|
||||
|
||||
`OAuth2TokenEndpointConfigurer` provides the following configuration options:
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
authorizationServerConfigurer
|
||||
.tokenEndpoint(tokenEndpoint ->
|
||||
tokenEndpoint
|
||||
.accessTokenRequestConverter(accessTokenRequestConverter) <1>
|
||||
.accessTokenRequestConverters(accessTokenRequestConvertersConsumer) <2>
|
||||
.authenticationProvider(authenticationProvider) <3>
|
||||
.authenticationProviders(authenticationProvidersConsumer) <4>
|
||||
.accessTokenResponseHandler(accessTokenResponseHandler) <5>
|
||||
.errorResponseHandler(errorResponseHandler) <6>
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
<1> `accessTokenRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3[OAuth2 access token request] from `HttpServletRequest` to an instance of `OAuth2AuthorizationGrantAuthenticationToken`.
|
||||
<2> `accessTokenRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
|
||||
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationGrantAuthenticationToken`.
|
||||
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
|
||||
<5> `accessTokenResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an `OAuth2AccessTokenAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.1[`OAuth2AccessTokenResponse`].
|
||||
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.2[OAuth2Error response].
|
||||
|
||||
`OAuth2TokenEndpointConfigurer` configures the `OAuth2TokenEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
|
||||
`OAuth2TokenEndpointFilter` is the `Filter` that processes OAuth2 access token requests.
|
||||
|
||||
The supported https://datatracker.ietf.org/doc/html/rfc6749#section-1.3[authorization grant types] are `authorization_code`, `refresh_token`, and `client_credentials`.
|
||||
|
||||
`OAuth2TokenEndpointFilter` is configured with the following defaults:
|
||||
|
||||
* `*AuthenticationConverter*` -- A `DelegatingAuthenticationConverter` composed of `OAuth2AuthorizationCodeAuthenticationConverter`, `OAuth2RefreshTokenAuthenticationConverter`, and `OAuth2ClientCredentialsAuthenticationConverter`.
|
||||
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2AuthorizationCodeAuthenticationProvider`, `OAuth2RefreshTokenAuthenticationProvider`, and `OAuth2ClientCredentialsAuthenticationProvider`.
|
||||
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an `OAuth2AccessTokenAuthenticationToken` and returns the `OAuth2AccessTokenResponse`.
|
||||
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
|
||||
|
||||
[[oauth2-token-introspection-endpoint]]
|
||||
== OAuth2 Token Introspection Endpoint
|
||||
|
||||
`OAuth2TokenIntrospectionEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc7662#section-2[OAuth2 Token Introspection endpoint].
|
||||
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for https://datatracker.ietf.org/doc/html/rfc7662#section-2.1[OAuth2 introspection requests].
|
||||
|
||||
`OAuth2TokenIntrospectionEndpointConfigurer` provides the following configuration options:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
authorizationServerConfigurer
|
||||
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint ->
|
||||
tokenIntrospectionEndpoint
|
||||
.introspectionRequestConverter(introspectionRequestConverter) <1>
|
||||
.introspectionRequestConverters(introspectionRequestConvertersConsumer) <2>
|
||||
.authenticationProvider(authenticationProvider) <3>
|
||||
.authenticationProviders(authenticationProvidersConsumer) <4>
|
||||
.introspectionResponseHandler(introspectionResponseHandler) <5>
|
||||
.errorResponseHandler(errorResponseHandler) <6>
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
<1> `introspectionRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc7662#section-2.1[OAuth2 introspection request] from `HttpServletRequest` to an instance of `OAuth2TokenIntrospectionAuthenticationToken`.
|
||||
<2> `introspectionRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
|
||||
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2TokenIntrospectionAuthenticationToken`.
|
||||
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
|
||||
<5> `introspectionResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2TokenIntrospectionAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc7662#section-2.2[OAuth2TokenIntrospection response].
|
||||
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc7662#section-2.3[OAuth2Error response].
|
||||
|
||||
`OAuth2TokenIntrospectionEndpointConfigurer` configures the `OAuth2TokenIntrospectionEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
|
||||
`OAuth2TokenIntrospectionEndpointFilter` is the `Filter` that processes OAuth2 introspection requests.
|
||||
|
||||
`OAuth2TokenIntrospectionEndpointFilter` is configured with the following defaults:
|
||||
|
||||
* `*AuthenticationConverter*` -- An `OAuth2TokenIntrospectionAuthenticationConverter`.
|
||||
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2TokenIntrospectionAuthenticationProvider`.
|
||||
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2TokenIntrospectionAuthenticationToken` and returns the `OAuth2TokenIntrospection` response.
|
||||
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
|
||||
|
||||
[[oauth2-token-revocation-endpoint]]
|
||||
== OAuth2 Token Revocation Endpoint
|
||||
|
||||
`OAuth2TokenRevocationEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc7009#section-2[OAuth2 Token Revocation endpoint].
|
||||
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for https://datatracker.ietf.org/doc/html/rfc7009#section-2.1[OAuth2 revocation requests].
|
||||
|
||||
`OAuth2TokenRevocationEndpointConfigurer` provides the following configuration options:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
authorizationServerConfigurer
|
||||
.tokenRevocationEndpoint(tokenRevocationEndpoint ->
|
||||
tokenRevocationEndpoint
|
||||
.revocationRequestConverter(revocationRequestConverter) <1>
|
||||
.revocationRequestConverters(revocationRequestConvertersConsumer) <2>
|
||||
.authenticationProvider(authenticationProvider) <3>
|
||||
.authenticationProviders(authenticationProvidersConsumer) <4>
|
||||
.revocationResponseHandler(revocationResponseHandler) <5>
|
||||
.errorResponseHandler(errorResponseHandler) <6>
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
<1> `revocationRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc7009#section-2.1[OAuth2 revocation request] from `HttpServletRequest` to an instance of `OAuth2TokenRevocationAuthenticationToken`.
|
||||
<2> `revocationRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
|
||||
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2TokenRevocationAuthenticationToken`.
|
||||
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
|
||||
<5> `revocationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2TokenRevocationAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc7009#section-2.2[OAuth2 revocation response].
|
||||
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc7009#section-2.2.1[OAuth2Error response].
|
||||
|
||||
`OAuth2TokenRevocationEndpointConfigurer` configures the `OAuth2TokenRevocationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
|
||||
`OAuth2TokenRevocationEndpointFilter` is the `Filter` that processes OAuth2 revocation requests.
|
||||
|
||||
`OAuth2TokenRevocationEndpointFilter` is configured with the following defaults:
|
||||
|
||||
* `*AuthenticationConverter*` -- An `OAuth2TokenRevocationAuthenticationConverter`.
|
||||
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2TokenRevocationAuthenticationProvider`.
|
||||
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2TokenRevocationAuthenticationToken` and returns the OAuth2 revocation response.
|
||||
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
|
||||
|
||||
[[oauth2-authorization-server-metadata-endpoint]]
|
||||
== OAuth2 Authorization Server Metadata Endpoint
|
||||
|
||||
`OAuth2AuthorizationServerMetadataEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc8414#section-3[OAuth2 Authorization Server Metadata endpoint].
|
||||
It defines an extension point that lets you customize the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2 Authorization Server Metadata response].
|
||||
|
||||
`OAuth2AuthorizationServerMetadataEndpointConfigurer` provides the following configuration option:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
authorizationServerConfigurer
|
||||
.authorizationServerMetadataEndpoint(authorizationServerMetadataEndpoint ->
|
||||
authorizationServerMetadataEndpoint
|
||||
.authorizationServerMetadataCustomizer(authorizationServerMetadataCustomizer)); <1>
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
<1> `authorizationServerMetadataCustomizer()`: The `Consumer` providing access to the `OAuth2AuthorizationServerMetadata.Builder` allowing the ability to customize the claims of the Authorization Server's configuration.
|
||||
|
||||
`OAuth2AuthorizationServerMetadataEndpointConfigurer` configures the `OAuth2AuthorizationServerMetadataEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
|
||||
`OAuth2AuthorizationServerMetadataEndpointFilter` is the `Filter` that returns the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2AuthorizationServerMetadata response].
|
||||
|
||||
[[jwk-set-endpoint]]
|
||||
== JWK Set Endpoint
|
||||
|
||||
`OAuth2AuthorizationServerConfigurer` provides support for the https://datatracker.ietf.org/doc/html/rfc7517[JWK Set endpoint].
|
||||
|
||||
`OAuth2AuthorizationServerConfigurer` configures the `NimbusJwkSetEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
|
||||
`NimbusJwkSetEndpointFilter` is the `Filter` that returns the https://datatracker.ietf.org/doc/html/rfc7517#section-5[JWK Set].
|
||||
|
||||
[NOTE]
|
||||
The JWK Set endpoint is configured *only* if a `JWKSource<SecurityContext>` `@Bean` is registered.
|
||||
|
||||
[[oidc-provider-configuration-endpoint]]
|
||||
== OpenID Connect 1.0 Provider Configuration Endpoint
|
||||
|
||||
`OidcProviderConfigurationEndpointConfigurer` provides the ability to customize the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[OpenID Connect 1.0 Provider Configuration endpoint].
|
||||
It defines an extension point that lets you customize the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse[OpenID Provider Configuration response].
|
||||
|
||||
`OidcProviderConfigurationEndpointConfigurer` provides the following configuration option:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
authorizationServerConfigurer
|
||||
.oidc(oidc ->
|
||||
oidc
|
||||
.providerConfigurationEndpoint(providerConfigurationEndpoint ->
|
||||
providerConfigurationEndpoint
|
||||
.providerConfigurationCustomizer(providerConfigurationCustomizer) <1>
|
||||
)
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
<1> `providerConfigurationCustomizer()`: The `Consumer` providing access to the `OidcProviderConfiguration.Builder` allowing the ability to customize the claims of the OpenID Provider's configuration.
|
||||
|
||||
`OidcProviderConfigurationEndpointConfigurer` configures the `OidcProviderConfigurationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
|
||||
`OidcProviderConfigurationEndpointFilter` is the `Filter` that returns the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse[OidcProviderConfiguration response].
|
||||
|
||||
[[oidc-user-info-endpoint]]
|
||||
== OpenID Connect 1.0 UserInfo Endpoint
|
||||
|
||||
`OidcUserInfoEndpointConfigurer` provides the ability to customize the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[OpenID Connect 1.0 UserInfo endpoint].
|
||||
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest[UserInfo requests].
|
||||
|
||||
`OidcUserInfoEndpointConfigurer` provides the following configuration options:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
authorizationServerConfigurer
|
||||
.oidc(oidc ->
|
||||
oidc
|
||||
.userInfoEndpoint(userInfoEndpoint ->
|
||||
userInfoEndpoint
|
||||
.userInfoRequestConverter(userInfoRequestConverter) <1>
|
||||
.userInfoRequestConverters(userInfoRequestConvertersConsumer) <2>
|
||||
.authenticationProvider(authenticationProvider) <3>
|
||||
.authenticationProviders(authenticationProvidersConsumer) <4>
|
||||
.userInfoResponseHandler(userInfoResponseHandler) <5>
|
||||
.errorResponseHandler(errorResponseHandler) <6>
|
||||
.userInfoMapper(userInfoMapper) <7>
|
||||
)
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
<1> `userInfoRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest[UserInfo request] from `HttpServletRequest` to an instance of `OidcUserInfoAuthenticationToken`.
|
||||
<2> `userInfoRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
|
||||
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OidcUserInfoAuthenticationToken`.
|
||||
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
|
||||
<5> `userInfoResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OidcUserInfoAuthenticationToken` and returning the https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse[UserInfo response].
|
||||
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://openid.net/specs/openid-connect-core-1_0.html#UserInfoError[UserInfo Error response].
|
||||
<7> `userInfoMapper()`: The `Function` used to extract claims from `OidcUserInfoAuthenticationContext` to an instance of `OidcUserInfo`.
|
||||
|
||||
`OidcUserInfoEndpointConfigurer` configures the `OidcUserInfoEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
|
||||
`OidcUserInfoEndpointFilter` is the `Filter` that processes https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest[UserInfo requests] and returns the https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse[OidcUserInfo response].
|
||||
|
||||
`OidcUserInfoEndpointFilter` is configured with the following defaults:
|
||||
|
||||
* `*AuthenticationConverter*` -- An internal implementation that obtains the `Authentication` from the `SecurityContext` and creates an `OidcUserInfoAuthenticationToken` with the principal.
|
||||
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OidcUserInfoAuthenticationProvider`, which is associated with an internal implementation of `userInfoMapper` that extracts https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims[standard claims] from the https://openid.net/specs/openid-connect-core-1_0.html#IDToken[ID Token] based on the https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims[scopes requested] during authorization.
|
||||
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OidcUserInfoAuthenticationToken` and returns the `OidcUserInfo` response.
|
||||
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
|
||||
|
||||
[TIP]
|
||||
You can customize the ID Token by providing an xref:core-model-components.adoc#oauth2-token-customizer[`OAuth2TokenCustomizer<JwtEncodingContext>`] `@Bean`.
|
||||
|
||||
The OpenID Connect 1.0 UserInfo endpoint is an OAuth2 protected resource, which *REQUIRES* an access token to be sent as a bearer token in the https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest[UserInfo request].
|
||||
The following example shows how to enable the OAuth2 resource server configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
...
|
||||
|
||||
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
|
||||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
A `JwtDecoder` `@Bean` is *REQUIRED* for the OpenID Connect 1.0 UserInfo endpoint.
|
||||
|
||||
[TIP]
|
||||
The guide xref:guides/how-to-userinfo.adoc#how-to-userinfo[How-to: Customize the OpenID Connect 1.0 UserInfo response] contains examples of customizing the UserInfo endpoint.
|
||||
|
||||
[[oidc-client-registration-endpoint]]
|
||||
== OpenID Connect 1.0 Client Registration Endpoint
|
||||
|
||||
`OidcClientRegistrationEndpointConfigurer` provides the ability to customize the https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration[OpenID Connect 1.0 Client Registration endpoint].
|
||||
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest[Client Registration requests] or https://openid.net/specs/openid-connect-registration-1_0.html#ReadRequest[Client Read requests].
|
||||
|
||||
`OidcClientRegistrationEndpointConfigurer` provides the following configuration options:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
authorizationServerConfigurer
|
||||
.oidc(oidc ->
|
||||
oidc
|
||||
.clientRegistrationEndpoint(clientRegistrationEndpoint ->
|
||||
clientRegistrationEndpoint
|
||||
.clientRegistrationRequestConverter(clientRegistrationRequestConverter) <1>
|
||||
.clientRegistrationRequestConverters(clientRegistrationRequestConvertersConsumers) <2>
|
||||
.authenticationProvider(authenticationProvider) <3>
|
||||
.authenticationProviders(authenticationProvidersConsumer) <4>
|
||||
.clientRegistrationResponseHandler(clientRegistrationResponseHandler) <5>
|
||||
.errorResponseHandler(errorResponseHandler) <6>
|
||||
)
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
<1> `clientRegistrationRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract a https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest[Client Registration request] or https://openid.net/specs/openid-connect-registration-1_0.html#ReadRequest[Client Read request] from `HttpServletRequest` to an instance of `OidcClientRegistrationAuthenticationToken`.
|
||||
<2> `clientRegistrationRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
|
||||
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OidcClientRegistrationAuthenticationToken`.
|
||||
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
|
||||
<5> `clientRegistrationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OidcClientRegistrationAuthenticationToken` and returning the https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse[Client Registration response] or https://openid.net/specs/openid-connect-registration-1_0.html#ReadResponse[Client Read response].
|
||||
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError[Client Registration Error response] or https://openid.net/specs/openid-connect-registration-1_0.html#ReadError[Client Read Error response].
|
||||
|
||||
[NOTE]
|
||||
The OpenID Connect 1.0 Client Registration endpoint is disabled by default because many deployments do not require dynamic client registration.
|
||||
|
||||
`OidcClientRegistrationEndpointConfigurer` configures the `OidcClientRegistrationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
|
||||
`OidcClientRegistrationEndpointFilter` is the `Filter` that processes https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest[Client Registration requests] and returns the https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse[OidcClientRegistration response].
|
||||
|
||||
[TIP]
|
||||
`OidcClientRegistrationEndpointFilter` also processes https://openid.net/specs/openid-connect-registration-1_0.html#ReadRequest[Client Read requests] and returns the https://openid.net/specs/openid-connect-registration-1_0.html#ReadResponse[OidcClientRegistration response].
|
||||
|
||||
`OidcClientRegistrationEndpointFilter` is configured with the following defaults:
|
||||
|
||||
* `*AuthenticationConverter*` -- An `OidcClientRegistrationAuthenticationConverter`.
|
||||
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OidcClientRegistrationAuthenticationProvider` and `OidcClientConfigurationAuthenticationProvider`.
|
||||
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OidcClientRegistrationAuthenticationToken` and returns the `OidcClientRegistration` response.
|
||||
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
|
||||
|
||||
The OpenID Connect 1.0 Client Registration endpoint is an https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration[OAuth2 protected resource], which *REQUIRES* an access token to be sent as a bearer token in the Client Registration (or Client Read) request.
|
||||
|
||||
[IMPORTANT]
|
||||
The access token in a Client Registration request *REQUIRES* the OAuth2 scope `client.create`.
|
||||
|
||||
[IMPORTANT]
|
||||
The access token in a Client Read request *REQUIRES* the OAuth2 scope `client.read`.
|
||||
|
||||
The following example shows how to enable the OAuth2 resource server configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
http.apply(authorizationServerConfigurer);
|
||||
|
||||
...
|
||||
|
||||
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
|
||||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
A `JwtDecoder` `@Bean` is *REQUIRED* for the OpenID Connect 1.0 Client Registration endpoint.
|
||||
@@ -5,3 +5,4 @@
|
||||
^http://lists.webappsec.org/.*
|
||||
^http://webblaze.cs.berkeley.edu/.*
|
||||
^http://www.w3.org/2000/09/xmldsig.*
|
||||
^http://www.gnu.org/.*
|
||||
@@ -1,5 +1,11 @@
|
||||
version=0.2.1
|
||||
springBootVersion=2.5.7
|
||||
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
|
||||
version=0.4.0-RC1
|
||||
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
springFrameworkVersion=5.3.23
|
||||
springSecurityVersion=5.8.0-RC1
|
||||
springJavaformatVersion=0.0.31
|
||||
springJavaformatExcludePackages=org/springframework/security/config org/springframework/security/oauth2
|
||||
checkstyleToolVersion=8.34
|
||||
nohttpCheckstyleVersion=0.0.10
|
||||
jacocoToolVersion=0.8.7
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
if (!project.hasProperty("springVersion")) {
|
||||
ext.springVersion = "5.3.13"
|
||||
}
|
||||
|
||||
if (!project.hasProperty("springSecurityVersion")) {
|
||||
ext.springSecurityVersion = "5.5.3"
|
||||
}
|
||||
|
||||
if (!project.hasProperty("reactorVersion")) {
|
||||
ext.reactorVersion = "2020.0.13"
|
||||
}
|
||||
|
||||
if (!project.hasProperty("locksDisabled")) {
|
||||
dependencyLocking {
|
||||
lockAllConfigurations()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom "org.springframework:spring-framework-bom:$springVersion"
|
||||
mavenBom "org.springframework.security:spring-security-bom:$springSecurityVersion"
|
||||
mavenBom "io.projectreactor:reactor-bom:$reactorVersion"
|
||||
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.5"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
dependency "com.nimbusds:nimbus-jose-jwt:9.10.1"
|
||||
dependency "javax.servlet:javax.servlet-api:4.0.1"
|
||||
dependency 'junit:junit:4.13.2'
|
||||
dependency 'org.assertj:assertj-core:3.19.0'
|
||||
dependency 'org.mockito:mockito-core:3.9.0'
|
||||
dependency "com.squareup.okhttp3:mockwebserver:3.14.9"
|
||||
dependency "com.squareup.okhttp3:okhttp:3.14.9"
|
||||
dependency "com.jayway.jsonpath:json-path:2.5.0"
|
||||
dependency "org.hsqldb:hsqldb:2.5.2"
|
||||
}
|
||||
}
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
apply plugin: 'io.spring.convention.spring-module'
|
||||
plugins {
|
||||
id "io.spring.convention.spring-module"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.springframework.security:spring-security-config'
|
||||
compile 'org.springframework.security:spring-security-web'
|
||||
compile 'org.springframework.security:spring-security-oauth2-core'
|
||||
compile 'org.springframework.security:spring-security-oauth2-jose'
|
||||
compile 'org.springframework.security:spring-security-oauth2-resource-server'
|
||||
compile springCoreDependency
|
||||
compile 'com.nimbusds:nimbus-jose-jwt'
|
||||
compile 'com.fasterxml.jackson.core:jackson-databind'
|
||||
management platform(project(":spring-authorization-server-dependencies"))
|
||||
|
||||
optional 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
|
||||
optional 'org.springframework:spring-jdbc'
|
||||
api "org.springframework.security:spring-security-config"
|
||||
api "org.springframework.security:spring-security-web"
|
||||
api "org.springframework.security:spring-security-oauth2-core"
|
||||
api "org.springframework.security:spring-security-oauth2-jose"
|
||||
api "org.springframework.security:spring-security-oauth2-resource-server"
|
||||
api("org.springframework:spring-core") {
|
||||
exclude group: "commons-logging", module: "commons-logging"
|
||||
}
|
||||
api "com.nimbusds:nimbus-jose-jwt"
|
||||
api "com.fasterxml.jackson.core:jackson-databind"
|
||||
|
||||
testCompile 'org.springframework.security:spring-security-test'
|
||||
testCompile 'org.springframework:spring-webmvc'
|
||||
testCompile 'junit:junit'
|
||||
testCompile 'org.assertj:assertj-core'
|
||||
testCompile 'org.mockito:mockito-core'
|
||||
testCompile 'com.jayway.jsonpath:json-path'
|
||||
optional "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
|
||||
optional "org.springframework:spring-jdbc"
|
||||
|
||||
testRuntime 'org.hsqldb:hsqldb'
|
||||
testImplementation "org.springframework.security:spring-security-test"
|
||||
testImplementation "org.springframework:spring-webmvc"
|
||||
testImplementation "junit:junit"
|
||||
testImplementation "org.assertj:assertj-core"
|
||||
testImplementation "org.mockito:mockito-core"
|
||||
testImplementation "com.jayway.jsonpath:json-path"
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver"
|
||||
|
||||
provided 'javax.servlet:javax.servlet-api'
|
||||
}
|
||||
|
||||
jacoco {
|
||||
toolVersion = '0.8.6'
|
||||
testRuntimeOnly "org.hsqldb:hsqldb"
|
||||
|
||||
provided "javax.servlet:javax.servlet-api"
|
||||
}
|
||||
|
||||
@@ -1,288 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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 java.net.URI;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider;
|
||||
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.config.ProviderSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter;
|
||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
||||
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
|
||||
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.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link AbstractHttpConfigurer} for OAuth 2.0 Authorization Server support.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @author Gerardo Roza
|
||||
* @author Ovidiu Popa
|
||||
* @since 0.0.1
|
||||
* @see AbstractHttpConfigurer
|
||||
* @see OAuth2ClientAuthenticationConfigurer
|
||||
* @see OAuth2AuthorizationEndpointConfigurer
|
||||
* @see OAuth2TokenEndpointConfigurer
|
||||
* @see OidcConfigurer
|
||||
* @see RegisteredClientRepository
|
||||
* @see OAuth2AuthorizationService
|
||||
* @see OAuth2AuthorizationConsentService
|
||||
* @see OAuth2TokenIntrospectionEndpointFilter
|
||||
* @see OAuth2TokenRevocationEndpointFilter
|
||||
* @see NimbusJwkSetEndpointFilter
|
||||
* @see OAuth2AuthorizationServerMetadataEndpointFilter
|
||||
*/
|
||||
public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> {
|
||||
|
||||
private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = createConfigurers();
|
||||
private RequestMatcher tokenIntrospectionEndpointMatcher;
|
||||
private RequestMatcher tokenRevocationEndpointMatcher;
|
||||
private RequestMatcher jwkSetEndpointMatcher;
|
||||
private RequestMatcher authorizationServerMetadataEndpointMatcher;
|
||||
private final RequestMatcher endpointsMatcher = (request) ->
|
||||
getRequestMatcher(OAuth2AuthorizationEndpointConfigurer.class).matches(request) ||
|
||||
getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) ||
|
||||
getRequestMatcher(OidcConfigurer.class).matches(request) ||
|
||||
this.tokenIntrospectionEndpointMatcher.matches(request) ||
|
||||
this.tokenRevocationEndpointMatcher.matches(request) ||
|
||||
this.jwkSetEndpointMatcher.matches(request) ||
|
||||
this.authorizationServerMetadataEndpointMatcher.matches(request);
|
||||
|
||||
/**
|
||||
* Sets the repository of registered clients.
|
||||
*
|
||||
* @param registeredClientRepository the repository of registered clients
|
||||
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfigurer<B> registeredClientRepository(RegisteredClientRepository registeredClientRepository) {
|
||||
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
|
||||
getBuilder().setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the authorization service.
|
||||
*
|
||||
* @param authorizationService the authorization service
|
||||
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfigurer<B> authorizationService(OAuth2AuthorizationService authorizationService) {
|
||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||
getBuilder().setSharedObject(OAuth2AuthorizationService.class, authorizationService);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the authorization consent service.
|
||||
*
|
||||
* @param authorizationConsentService the authorization consent service
|
||||
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfigurer<B> authorizationConsentService(OAuth2AuthorizationConsentService authorizationConsentService) {
|
||||
Assert.notNull(authorizationConsentService, "authorizationConsentService cannot be null");
|
||||
getBuilder().setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the provider settings.
|
||||
*
|
||||
* @param providerSettings the provider settings
|
||||
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfigurer<B> providerSettings(ProviderSettings providerSettings) {
|
||||
Assert.notNull(providerSettings, "providerSettings cannot be null");
|
||||
getBuilder().setSharedObject(ProviderSettings.class, providerSettings);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures OAuth 2.0 Client Authentication.
|
||||
*
|
||||
* @param clientAuthenticationCustomizer the {@link Customizer} providing access to the {@link OAuth2ClientAuthenticationConfigurer}
|
||||
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfigurer<B> clientAuthentication(Customizer<OAuth2ClientAuthenticationConfigurer> clientAuthenticationCustomizer) {
|
||||
clientAuthenticationCustomizer.customize(getConfigurer(OAuth2ClientAuthenticationConfigurer.class));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the OAuth 2.0 Authorization Endpoint.
|
||||
*
|
||||
* @param authorizationEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2AuthorizationEndpointConfigurer}
|
||||
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfigurer<B> authorizationEndpoint(Customizer<OAuth2AuthorizationEndpointConfigurer> authorizationEndpointCustomizer) {
|
||||
authorizationEndpointCustomizer.customize(getConfigurer(OAuth2AuthorizationEndpointConfigurer.class));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the OAuth 2.0 Token Endpoint.
|
||||
*
|
||||
* @param tokenEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2TokenEndpointConfigurer}
|
||||
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfigurer<B> tokenEndpoint(Customizer<OAuth2TokenEndpointConfigurer> tokenEndpointCustomizer) {
|
||||
tokenEndpointCustomizer.customize(getConfigurer(OAuth2TokenEndpointConfigurer.class));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures OpenID Connect 1.0 support.
|
||||
*
|
||||
* @param oidcCustomizer the {@link Customizer} providing access to the {@link OidcConfigurer}
|
||||
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfigurer<B> oidc(Customizer<OidcConfigurer> oidcCustomizer) {
|
||||
oidcCustomizer.customize(getConfigurer(OidcConfigurer.class));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link RequestMatcher} for the authorization server endpoints.
|
||||
*
|
||||
* @return a {@link RequestMatcher} for the authorization server endpoints
|
||||
*/
|
||||
public RequestMatcher getEndpointsMatcher() {
|
||||
return this.endpointsMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(B builder) {
|
||||
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
|
||||
validateProviderSettings(providerSettings);
|
||||
initEndpointMatchers(providerSettings);
|
||||
|
||||
this.configurers.values().forEach(configurer -> configurer.init(builder));
|
||||
|
||||
OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider =
|
||||
new OAuth2TokenIntrospectionAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(builder));
|
||||
builder.authenticationProvider(postProcess(tokenIntrospectionAuthenticationProvider));
|
||||
|
||||
OAuth2TokenRevocationAuthenticationProvider tokenRevocationAuthenticationProvider =
|
||||
new OAuth2TokenRevocationAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(builder));
|
||||
builder.authenticationProvider(postProcess(tokenRevocationAuthenticationProvider));
|
||||
|
||||
ExceptionHandlingConfigurer<B> exceptionHandling = builder.getConfigurer(ExceptionHandlingConfigurer.class);
|
||||
if (exceptionHandling != null) {
|
||||
exceptionHandling.defaultAuthenticationEntryPointFor(
|
||||
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
|
||||
new OrRequestMatcher(
|
||||
getRequestMatcher(OAuth2TokenEndpointConfigurer.class),
|
||||
this.tokenIntrospectionEndpointMatcher,
|
||||
this.tokenRevocationEndpointMatcher)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(B builder) {
|
||||
this.configurers.values().forEach(configurer -> configurer.configure(builder));
|
||||
|
||||
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
|
||||
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
|
||||
|
||||
OAuth2TokenIntrospectionEndpointFilter tokenIntrospectionEndpointFilter =
|
||||
new OAuth2TokenIntrospectionEndpointFilter(
|
||||
authenticationManager,
|
||||
providerSettings.getTokenIntrospectionEndpoint());
|
||||
builder.addFilterAfter(postProcess(tokenIntrospectionEndpointFilter), FilterSecurityInterceptor.class);
|
||||
|
||||
OAuth2TokenRevocationEndpointFilter tokenRevocationEndpointFilter =
|
||||
new OAuth2TokenRevocationEndpointFilter(
|
||||
authenticationManager,
|
||||
providerSettings.getTokenRevocationEndpoint());
|
||||
builder.addFilterAfter(postProcess(tokenRevocationEndpointFilter), FilterSecurityInterceptor.class);
|
||||
|
||||
NimbusJwkSetEndpointFilter jwkSetEndpointFilter =
|
||||
new NimbusJwkSetEndpointFilter(
|
||||
OAuth2ConfigurerUtils.getJwkSource(builder),
|
||||
providerSettings.getJwkSetEndpoint());
|
||||
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
|
||||
if (providerSettings.getIssuer() != null) {
|
||||
OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
|
||||
new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);
|
||||
builder.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> createConfigurers() {
|
||||
Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>();
|
||||
configurers.put(OAuth2ClientAuthenticationConfigurer.class, new OAuth2ClientAuthenticationConfigurer(this::postProcess));
|
||||
configurers.put(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer(this::postProcess));
|
||||
configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess));
|
||||
configurers.put(OidcConfigurer.class, new OidcConfigurer(this::postProcess));
|
||||
return configurers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getConfigurer(Class<T> type) {
|
||||
return (T) this.configurers.get(type);
|
||||
}
|
||||
|
||||
private <T extends AbstractOAuth2Configurer> RequestMatcher getRequestMatcher(Class<T> configurerType) {
|
||||
return getConfigurer(configurerType).getRequestMatcher();
|
||||
}
|
||||
|
||||
private void initEndpointMatchers(ProviderSettings providerSettings) {
|
||||
this.tokenIntrospectionEndpointMatcher = new AntPathRequestMatcher(
|
||||
providerSettings.getTokenIntrospectionEndpoint(), HttpMethod.POST.name());
|
||||
this.tokenRevocationEndpointMatcher = new AntPathRequestMatcher(
|
||||
providerSettings.getTokenRevocationEndpoint(), HttpMethod.POST.name());
|
||||
this.jwkSetEndpointMatcher = new AntPathRequestMatcher(
|
||||
providerSettings.getJwkSetEndpoint(), HttpMethod.GET.name());
|
||||
this.authorizationServerMetadataEndpointMatcher = new AntPathRequestMatcher(
|
||||
"/.well-known/oauth-authorization-server", HttpMethod.GET.name());
|
||||
}
|
||||
|
||||
private static void validateProviderSettings(ProviderSettings providerSettings) {
|
||||
if (providerSettings.getIssuer() != null) {
|
||||
try {
|
||||
new URI(providerSettings.getIssuer()).toURL();
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException("issuer must be a valid URL", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* Configurer for OAuth 2.0 Client Authentication.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.2.0
|
||||
* @see OAuth2AuthorizationServerConfigurer#clientAuthentication
|
||||
* @see OAuth2ClientAuthenticationFilter
|
||||
*/
|
||||
public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Configurer {
|
||||
private RequestMatcher requestMatcher;
|
||||
private AuthenticationConverter authenticationConverter;
|
||||
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
|
||||
private AuthenticationSuccessHandler authenticationSuccessHandler;
|
||||
private AuthenticationFailureHandler errorResponseHandler;
|
||||
|
||||
/**
|
||||
* Restrict for internal use only.
|
||||
*/
|
||||
OAuth2ClientAuthenticationConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
|
||||
super(objectPostProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
|
||||
* to an instance of {@link OAuth2ClientAuthenticationToken} used for authenticating the client.
|
||||
*
|
||||
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
|
||||
* @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2ClientAuthenticationConfigurer authenticationConverter(AuthenticationConverter authenticationConverter) {
|
||||
this.authenticationConverter = authenticationConverter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an {@link AuthenticationProvider} used for authenticating an {@link OAuth2ClientAuthenticationToken}.
|
||||
*
|
||||
* @param authenticationProvider an {@link AuthenticationProvider} used for authenticating an {@link OAuth2ClientAuthenticationToken}
|
||||
* @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2ClientAuthenticationConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
|
||||
this.authenticationProviders.add(authenticationProvider);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationSuccessHandler} used for handling a successful client authentication
|
||||
* and associating the {@link OAuth2ClientAuthenticationToken} to the {@link SecurityContext}.
|
||||
*
|
||||
* @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used for handling a successful client authentication
|
||||
* @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2ClientAuthenticationConfigurer authenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
|
||||
this.authenticationSuccessHandler = authenticationSuccessHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationFailureHandler} used for handling a failed client authentication
|
||||
* and returning the {@link OAuth2Error Error Response}.
|
||||
*
|
||||
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling a failed client authentication
|
||||
* @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2ClientAuthenticationConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
|
||||
this.errorResponseHandler = errorResponseHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
<B extends HttpSecurityBuilder<B>> void init(B builder) {
|
||||
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(
|
||||
providerSettings.getTokenEndpoint(),
|
||||
HttpMethod.POST.name()),
|
||||
new AntPathRequestMatcher(
|
||||
providerSettings.getTokenIntrospectionEndpoint(),
|
||||
HttpMethod.POST.name()),
|
||||
new AntPathRequestMatcher(
|
||||
providerSettings.getTokenRevocationEndpoint(),
|
||||
HttpMethod.POST.name()));
|
||||
|
||||
List<AuthenticationProvider> authenticationProviders =
|
||||
!this.authenticationProviders.isEmpty() ?
|
||||
this.authenticationProviders :
|
||||
createDefaultAuthenticationProviders(builder);
|
||||
authenticationProviders.forEach(authenticationProvider ->
|
||||
builder.authenticationProvider(postProcess(authenticationProvider)));
|
||||
}
|
||||
|
||||
@Override
|
||||
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
|
||||
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
|
||||
OAuth2ClientAuthenticationFilter clientAuthenticationFilter = new OAuth2ClientAuthenticationFilter(
|
||||
authenticationManager, this.requestMatcher);
|
||||
if (this.authenticationConverter != null) {
|
||||
clientAuthenticationFilter.setAuthenticationConverter(this.authenticationConverter);
|
||||
}
|
||||
if (this.authenticationSuccessHandler != null) {
|
||||
clientAuthenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
|
||||
}
|
||||
if (this.errorResponseHandler != null) {
|
||||
clientAuthenticationFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
|
||||
}
|
||||
builder.addFilterAfter(postProcess(clientAuthenticationFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
RequestMatcher getRequestMatcher() {
|
||||
return this.requestMatcher;
|
||||
}
|
||||
|
||||
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
|
||||
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
|
||||
|
||||
OAuth2ClientAuthenticationProvider clientAuthenticationProvider =
|
||||
new OAuth2ClientAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(builder));
|
||||
PasswordEncoder passwordEncoder = OAuth2ConfigurerUtils.getOptionalBean(builder, PasswordEncoder.class);
|
||||
if (passwordEncoder != null) {
|
||||
clientAuthenticationProvider.setPasswordEncoder(passwordEncoder);
|
||||
}
|
||||
authenticationProviders.add(clientAuthenticationProvider);
|
||||
|
||||
return authenticationProviders;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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 java.util.Map;
|
||||
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Utility methods for the OAuth 2.0 Configurers.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.2
|
||||
*/
|
||||
final class OAuth2ConfigurerUtils {
|
||||
|
||||
private OAuth2ConfigurerUtils() {
|
||||
}
|
||||
|
||||
static <B extends HttpSecurityBuilder<B>> RegisteredClientRepository getRegisteredClientRepository(B builder) {
|
||||
RegisteredClientRepository registeredClientRepository = builder.getSharedObject(RegisteredClientRepository.class);
|
||||
if (registeredClientRepository == null) {
|
||||
registeredClientRepository = getBean(builder, RegisteredClientRepository.class);
|
||||
builder.setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
|
||||
}
|
||||
return registeredClientRepository;
|
||||
}
|
||||
|
||||
static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizationService getAuthorizationService(B builder) {
|
||||
OAuth2AuthorizationService authorizationService = builder.getSharedObject(OAuth2AuthorizationService.class);
|
||||
if (authorizationService == null) {
|
||||
authorizationService = getOptionalBean(builder, OAuth2AuthorizationService.class);
|
||||
if (authorizationService == null) {
|
||||
authorizationService = new InMemoryOAuth2AuthorizationService();
|
||||
}
|
||||
builder.setSharedObject(OAuth2AuthorizationService.class, authorizationService);
|
||||
}
|
||||
return authorizationService;
|
||||
}
|
||||
|
||||
static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizationConsentService getAuthorizationConsentService(B builder) {
|
||||
OAuth2AuthorizationConsentService authorizationConsentService = builder.getSharedObject(OAuth2AuthorizationConsentService.class);
|
||||
if (authorizationConsentService == null) {
|
||||
authorizationConsentService = getOptionalBean(builder, OAuth2AuthorizationConsentService.class);
|
||||
if (authorizationConsentService == null) {
|
||||
authorizationConsentService = new InMemoryOAuth2AuthorizationConsentService();
|
||||
}
|
||||
builder.setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);
|
||||
}
|
||||
return authorizationConsentService;
|
||||
}
|
||||
|
||||
static <B extends HttpSecurityBuilder<B>> JwtEncoder getJwtEncoder(B builder) {
|
||||
JwtEncoder jwtEncoder = builder.getSharedObject(JwtEncoder.class);
|
||||
if (jwtEncoder == null) {
|
||||
jwtEncoder = getOptionalBean(builder, JwtEncoder.class);
|
||||
if (jwtEncoder == null) {
|
||||
JWKSource<SecurityContext> jwkSource = getJwkSource(builder);
|
||||
jwtEncoder = new NimbusJwsEncoder(jwkSource);
|
||||
}
|
||||
builder.setSharedObject(JwtEncoder.class, jwtEncoder);
|
||||
}
|
||||
return jwtEncoder;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <B extends HttpSecurityBuilder<B>> JWKSource<SecurityContext> getJwkSource(B builder) {
|
||||
JWKSource<SecurityContext> jwkSource = builder.getSharedObject(JWKSource.class);
|
||||
if (jwkSource == null) {
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(JWKSource.class, SecurityContext.class);
|
||||
jwkSource = getBean(builder, type);
|
||||
builder.setSharedObject(JWKSource.class, jwkSource);
|
||||
}
|
||||
return jwkSource;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <B extends HttpSecurityBuilder<B>> OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(B builder) {
|
||||
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = builder.getSharedObject(OAuth2TokenCustomizer.class);
|
||||
if (jwtCustomizer == null) {
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class);
|
||||
jwtCustomizer = getOptionalBean(builder, type);
|
||||
if (jwtCustomizer != null) {
|
||||
builder.setSharedObject(OAuth2TokenCustomizer.class, jwtCustomizer);
|
||||
}
|
||||
}
|
||||
return jwtCustomizer;
|
||||
}
|
||||
|
||||
static <B extends HttpSecurityBuilder<B>> ProviderSettings getProviderSettings(B builder) {
|
||||
ProviderSettings providerSettings = builder.getSharedObject(ProviderSettings.class);
|
||||
if (providerSettings == null) {
|
||||
providerSettings = getOptionalBean(builder, ProviderSettings.class);
|
||||
if (providerSettings == null) {
|
||||
providerSettings = ProviderSettings.builder().build();
|
||||
}
|
||||
builder.setSharedObject(ProviderSettings.class, providerSettings);
|
||||
}
|
||||
return providerSettings;
|
||||
}
|
||||
|
||||
static <B extends HttpSecurityBuilder<B>, T> T getBean(B builder, Class<T> type) {
|
||||
return builder.getSharedObject(ApplicationContext.class).getBean(type);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <B extends HttpSecurityBuilder<B>, T> T getBean(B builder, ResolvableType type) {
|
||||
ApplicationContext context = builder.getSharedObject(ApplicationContext.class);
|
||||
String[] names = context.getBeanNamesForType(type);
|
||||
if (names.length == 1) {
|
||||
return (T) context.getBean(names[0]);
|
||||
}
|
||||
if (names.length > 1) {
|
||||
throw new NoUniqueBeanDefinitionException(type, names);
|
||||
}
|
||||
throw new NoSuchBeanDefinitionException(type);
|
||||
}
|
||||
|
||||
static <B extends HttpSecurityBuilder<B>, T> T getOptionalBean(B builder, Class<T> type) {
|
||||
Map<String, T> beansMap = BeanFactoryUtils.beansOfTypeIncludingAncestors(
|
||||
builder.getSharedObject(ApplicationContext.class), type);
|
||||
if (beansMap.size() > 1) {
|
||||
throw new NoUniqueBeanDefinitionException(type, beansMap.size(),
|
||||
"Expected single matching bean of type '" + type.getName() + "' but found " +
|
||||
beansMap.size() + ": " + StringUtils.collectionToCommaDelimitedString(beansMap.keySet()));
|
||||
}
|
||||
return (!beansMap.isEmpty() ? beansMap.values().iterator().next() : null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <B extends HttpSecurityBuilder<B>, T> T getOptionalBean(B builder, ResolvableType type) {
|
||||
ApplicationContext context = builder.getSharedObject(ApplicationContext.class);
|
||||
String[] names = context.getBeanNamesForType(type);
|
||||
if (names.length > 1) {
|
||||
throw new NoUniqueBeanDefinitionException(type, names);
|
||||
}
|
||||
return names.length == 1 ? (T) context.getBean(names[0]) : null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
|
||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* Configurer for OpenID Connect Dynamic Client Registration 1.0 Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.2.0
|
||||
* @see OidcConfigurer#clientRegistrationEndpoint
|
||||
* @see OidcClientRegistrationEndpointFilter
|
||||
*/
|
||||
public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAuth2Configurer {
|
||||
private RequestMatcher requestMatcher;
|
||||
|
||||
/**
|
||||
* Restrict for internal use only.
|
||||
*/
|
||||
OidcClientRegistrationEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
|
||||
super(objectPostProcessor);
|
||||
}
|
||||
|
||||
@Override
|
||||
<B extends HttpSecurityBuilder<B>> void init(B builder) {
|
||||
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(providerSettings.getOidcClientRegistrationEndpoint(), HttpMethod.POST.name()),
|
||||
new AntPathRequestMatcher(providerSettings.getOidcClientRegistrationEndpoint(), HttpMethod.GET.name())
|
||||
);
|
||||
|
||||
OidcClientRegistrationAuthenticationProvider oidcClientRegistrationAuthenticationProvider =
|
||||
new OidcClientRegistrationAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(builder),
|
||||
OAuth2ConfigurerUtils.getJwtEncoder(builder));
|
||||
builder.authenticationProvider(postProcess(oidcClientRegistrationAuthenticationProvider));
|
||||
}
|
||||
|
||||
@Override
|
||||
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
|
||||
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
|
||||
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
|
||||
|
||||
OidcClientRegistrationEndpointFilter oidcClientRegistrationEndpointFilter =
|
||||
new OidcClientRegistrationEndpointFilter(
|
||||
authenticationManager,
|
||||
providerSettings.getOidcClientRegistrationEndpoint());
|
||||
builder.addFilterAfter(postProcess(oidcClientRegistrationEndpointFilter), FilterSecurityInterceptor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
RequestMatcher getRequestMatcher() {
|
||||
return this.requestMatcher;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
|
||||
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* Configurer for OpenID Connect 1.0 support.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.2.0
|
||||
* @see OAuth2AuthorizationServerConfigurer#oidc
|
||||
* @see OidcClientRegistrationEndpointConfigurer
|
||||
* @see OidcUserInfoEndpointConfigurer
|
||||
* @see OidcProviderConfigurationEndpointFilter
|
||||
*/
|
||||
public final class OidcConfigurer extends AbstractOAuth2Configurer {
|
||||
private final OidcUserInfoEndpointConfigurer userInfoEndpointConfigurer;
|
||||
private OidcClientRegistrationEndpointConfigurer clientRegistrationEndpointConfigurer;
|
||||
private RequestMatcher requestMatcher;
|
||||
|
||||
/**
|
||||
* Restrict for internal use only.
|
||||
*/
|
||||
OidcConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
|
||||
super(objectPostProcessor);
|
||||
this.userInfoEndpointConfigurer = new OidcUserInfoEndpointConfigurer(objectPostProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the OpenID Connect Dynamic Client Registration 1.0 Endpoint.
|
||||
*
|
||||
* @param clientRegistrationEndpointCustomizer the {@link Customizer} providing access to the {@link OidcClientRegistrationEndpointConfigurer}
|
||||
* @return the {@link OidcConfigurer} for further configuration
|
||||
*/
|
||||
public OidcConfigurer clientRegistrationEndpoint(Customizer<OidcClientRegistrationEndpointConfigurer> clientRegistrationEndpointCustomizer) {
|
||||
if (this.clientRegistrationEndpointConfigurer == null) {
|
||||
this.clientRegistrationEndpointConfigurer = new OidcClientRegistrationEndpointConfigurer(getObjectPostProcessor());
|
||||
}
|
||||
clientRegistrationEndpointCustomizer.customize(this.clientRegistrationEndpointConfigurer);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the OpenID Connect 1.0 UserInfo Endpoint.
|
||||
*
|
||||
* @param userInfoEndpointCustomizer the {@link Customizer} providing access to the {@link OidcUserInfoEndpointConfigurer}
|
||||
* @return the {@link OidcConfigurer} for further configuration
|
||||
*/
|
||||
public OidcConfigurer userInfoEndpoint(Customizer<OidcUserInfoEndpointConfigurer> userInfoEndpointCustomizer) {
|
||||
userInfoEndpointCustomizer.customize(this.userInfoEndpointConfigurer);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
<B extends HttpSecurityBuilder<B>> void init(B builder) {
|
||||
this.userInfoEndpointConfigurer.init(builder);
|
||||
if (this.clientRegistrationEndpointConfigurer != null) {
|
||||
this.clientRegistrationEndpointConfigurer.init(builder);
|
||||
}
|
||||
|
||||
List<RequestMatcher> requestMatchers = new ArrayList<>();
|
||||
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
|
||||
if (providerSettings.getIssuer() != null) {
|
||||
requestMatchers.add(new AntPathRequestMatcher(
|
||||
"/.well-known/openid-configuration", HttpMethod.GET.name()));
|
||||
}
|
||||
requestMatchers.add(this.userInfoEndpointConfigurer.getRequestMatcher());
|
||||
if (this.clientRegistrationEndpointConfigurer != null) {
|
||||
requestMatchers.add(this.clientRegistrationEndpointConfigurer.getRequestMatcher());
|
||||
}
|
||||
this.requestMatcher = requestMatchers.size() > 1 ? new OrRequestMatcher(requestMatchers) : requestMatchers.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
|
||||
this.userInfoEndpointConfigurer.configure(builder);
|
||||
if (this.clientRegistrationEndpointConfigurer != null) {
|
||||
this.clientRegistrationEndpointConfigurer.configure(builder);
|
||||
}
|
||||
|
||||
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
|
||||
if (providerSettings.getIssuer() != null) {
|
||||
OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter =
|
||||
new OidcProviderConfigurationEndpointFilter(providerSettings);
|
||||
builder.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
RequestMatcher getRequestMatcher() {
|
||||
return this.requestMatcher;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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 java.util.function.Function;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
|
||||
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter;
|
||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* Configurer for OpenID Connect 1.0 UserInfo Endpoint.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
* @since 0.2.1
|
||||
* @see OidcConfigurer#userInfoEndpoint
|
||||
* @see OidcUserInfoEndpointFilter
|
||||
*/
|
||||
public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configurer {
|
||||
private RequestMatcher requestMatcher;
|
||||
private Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper;
|
||||
|
||||
/**
|
||||
* Restrict for internal use only.
|
||||
*/
|
||||
OidcUserInfoEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
|
||||
super(objectPostProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Function} used to extract claims from {@link OidcUserInfoAuthenticationContext}
|
||||
* to an instance of {@link OidcUserInfo} for the UserInfo response.
|
||||
*
|
||||
* <p>
|
||||
* The {@link OidcUserInfoAuthenticationContext} gives the mapper access to the {@link OidcUserInfoAuthenticationToken},
|
||||
* as well as, the following context attributes:
|
||||
* <ul>
|
||||
* <li>{@link OidcUserInfoAuthenticationContext#getAccessToken()} containing the bearer token used to make the request.</li>
|
||||
* <li>{@link OidcUserInfoAuthenticationContext#getAuthorization()} containing the {@link OidcIdToken} and
|
||||
* {@link OAuth2AccessToken} associated with the bearer token used to make the request.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param userInfoMapper the {@link Function} used to extract claims from {@link OidcUserInfoAuthenticationContext} to an instance of {@link OidcUserInfo}
|
||||
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
|
||||
*/
|
||||
public OidcUserInfoEndpointConfigurer userInfoMapper(Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper) {
|
||||
this.userInfoMapper = userInfoMapper;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
<B extends HttpSecurityBuilder<B>> void init(B builder) {
|
||||
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
|
||||
String userInfoEndpointUri = providerSettings.getOidcUserInfoEndpoint();
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.POST.name()));
|
||||
|
||||
OidcUserInfoAuthenticationProvider oidcUserInfoAuthenticationProvider =
|
||||
new OidcUserInfoAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(builder));
|
||||
if (this.userInfoMapper != null) {
|
||||
oidcUserInfoAuthenticationProvider.setUserInfoMapper(this.userInfoMapper);
|
||||
}
|
||||
builder.authenticationProvider(postProcess(oidcUserInfoAuthenticationProvider));
|
||||
}
|
||||
|
||||
@Override
|
||||
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
|
||||
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
|
||||
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
|
||||
|
||||
OidcUserInfoEndpointFilter oidcUserInfoEndpointFilter =
|
||||
new OidcUserInfoEndpointFilter(
|
||||
authenticationManager,
|
||||
providerSettings.getOidcUserInfoEndpoint());
|
||||
builder.addFilterAfter(postProcess(oidcUserInfoEndpointFilter), FilterSecurityInterceptor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
RequestMatcher getRequestMatcher() {
|
||||
return this.requestMatcher;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-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.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* TODO
|
||||
* This class is "mostly" a copy from Spring Security and should be removed after upgrading to Spring Security 5.6.0 GA.
|
||||
* The major changes made between the Spring Security class and this one are:
|
||||
* 1) Class renamed from `OAuth2IntrospectionClaimAccessor` to `OAuth2TokenIntrospectionClaimAccessor`
|
||||
* 2) Moved from package `org.springframework.security.oauth2.server.resource.introspection` to `org.springframework.security.oauth2.core`
|
||||
*
|
||||
* gh-9647 Move and rename OAuth2IntrospectionClaimAccessor/Names
|
||||
* https://github.com/spring-projects/spring-security/issues/9647
|
||||
*/
|
||||
|
||||
/**
|
||||
* A {@link ClaimAccessor} for the "claims" that may be contained in the
|
||||
* Introspection Response.
|
||||
*
|
||||
* @author David Kovac
|
||||
* @since 5.4
|
||||
* @see ClaimAccessor
|
||||
* @see OAuth2TokenIntrospectionClaimNames
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc7662#section-2.2">Introspection Response</a>
|
||||
*/
|
||||
public interface OAuth2TokenIntrospectionClaimAccessor extends ClaimAccessor {
|
||||
|
||||
/**
|
||||
* Returns the indicator {@code (active)} whether or not the token is currently active
|
||||
* @return the indicator whether or not the token is currently active
|
||||
*/
|
||||
default boolean isActive() {
|
||||
return Boolean.TRUE.equals(getClaimAsBoolean(OAuth2TokenIntrospectionClaimNames.ACTIVE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scopes {@code (scope)} associated with the token
|
||||
* @return the scopes associated with the token
|
||||
*/
|
||||
default List<String> getScopes() {
|
||||
return getClaimAsStringList(OAuth2TokenIntrospectionClaimNames.SCOPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client identifier {@code (client_id)} for the token
|
||||
* @return the client identifier for the token
|
||||
*/
|
||||
default String getClientId() {
|
||||
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.CLIENT_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable identifier {@code (username)} for the resource owner that
|
||||
* authorized the token
|
||||
* @return a human-readable identifier for the resource owner that authorized the
|
||||
* token
|
||||
*/
|
||||
default String getUsername() {
|
||||
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.USERNAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the token {@code (token_type)}, for example {@code bearer}.
|
||||
* @return the type of the token, for example {@code bearer}.
|
||||
*/
|
||||
default String getTokenType() {
|
||||
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.TOKEN_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a timestamp {@code (exp)} indicating when the token expires
|
||||
* @return a timestamp indicating when the token expires
|
||||
*/
|
||||
default Instant getExpiresAt() {
|
||||
return getClaimAsInstant(OAuth2TokenIntrospectionClaimNames.EXP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a timestamp {@code (iat)} indicating when the token was issued
|
||||
* @return a timestamp indicating when the token was issued
|
||||
*/
|
||||
default Instant getIssuedAt() {
|
||||
return getClaimAsInstant(OAuth2TokenIntrospectionClaimNames.IAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a timestamp {@code (nbf)} indicating when the token is not to be used
|
||||
* before
|
||||
* @return a timestamp indicating when the token is not to be used before
|
||||
*/
|
||||
default Instant getNotBefore() {
|
||||
return getClaimAsInstant(OAuth2TokenIntrospectionClaimNames.NBF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns usually a machine-readable identifier {@code (sub)} of the resource owner
|
||||
* who authorized the token
|
||||
* @return usually a machine-readable identifier of the resource owner who authorized
|
||||
* the token
|
||||
*/
|
||||
default String getSubject() {
|
||||
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.SUB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the intended audience {@code (aud)} for the token
|
||||
* @return the intended audience for the token
|
||||
*/
|
||||
default List<String> getAudience() {
|
||||
return getClaimAsStringList(OAuth2TokenIntrospectionClaimNames.AUD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the issuer {@code (iss)} of the token
|
||||
* @return the issuer of the token
|
||||
*/
|
||||
default URL getIssuer() {
|
||||
return getClaimAsURL(OAuth2TokenIntrospectionClaimNames.ISS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier {@code (jti)} for the token
|
||||
* @return the identifier for the token
|
||||
*/
|
||||
default String getId() {
|
||||
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.JTI);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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 "mostly" a copy from Spring Security and should be removed after upgrading to Spring Security 5.6.0 GA.
|
||||
* The major changes made between the Spring Security class and this one are:
|
||||
* 1) Class renamed from `OAuth2IntrospectionClaimNames` to `OAuth2TokenIntrospectionClaimNames`
|
||||
* 2) Moved from package `org.springframework.security.oauth2.server.resource.introspection` to `org.springframework.security.oauth2.core`
|
||||
*
|
||||
* gh-9647 Move and rename OAuth2IntrospectionClaimAccessor/Names
|
||||
* https://github.com/spring-projects/spring-security/issues/9647
|
||||
*/
|
||||
|
||||
/**
|
||||
* The names of the "Introspection Claims" defined by an
|
||||
* <a target="_blank" href="https://tools.ietf.org/html/rfc7662#section-2.2">Introspection
|
||||
* Response</a>.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.2
|
||||
*/
|
||||
public interface OAuth2TokenIntrospectionClaimNames {
|
||||
|
||||
/**
|
||||
* {@code active} - Indicator whether or not the token is currently active
|
||||
*/
|
||||
String ACTIVE = "active";
|
||||
|
||||
/**
|
||||
* {@code scope} - The scopes for the token
|
||||
*/
|
||||
String SCOPE = "scope";
|
||||
|
||||
/**
|
||||
* {@code client_id} - The Client identifier for the token
|
||||
*/
|
||||
String CLIENT_ID = "client_id";
|
||||
|
||||
/**
|
||||
* {@code username} - A human-readable identifier for the resource owner that
|
||||
* authorized the token
|
||||
*/
|
||||
String USERNAME = "username";
|
||||
|
||||
/**
|
||||
* {@code token_type} - The type of the token, for example {@code bearer}.
|
||||
*/
|
||||
String TOKEN_TYPE = "token_type";
|
||||
|
||||
/**
|
||||
* {@code exp} - A timestamp indicating when the token expires
|
||||
*/
|
||||
String EXP = "exp";
|
||||
|
||||
/**
|
||||
* {@code iat} - A timestamp indicating when the token was issued
|
||||
*/
|
||||
String IAT = "iat";
|
||||
|
||||
/**
|
||||
* {@code nbf} - A timestamp indicating when the token is not to be used before
|
||||
*/
|
||||
String NBF = "nbf";
|
||||
|
||||
/**
|
||||
* {@code sub} - Usually a machine-readable identifier of the resource owner who
|
||||
* authorized the token
|
||||
*/
|
||||
String SUB = "sub";
|
||||
|
||||
/**
|
||||
* {@code aud} - The intended audience for the token
|
||||
*/
|
||||
String AUD = "aud";
|
||||
|
||||
/**
|
||||
* {@code iss} - The issuer of the token
|
||||
*/
|
||||
String ISS = "iss";
|
||||
|
||||
/**
|
||||
* {@code jti} - The identifier for the token
|
||||
*/
|
||||
String JTI = "jti";
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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.authentication;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are responsible for validating the attribute(s)
|
||||
* of the {@link Authentication} associated to the {@link OAuth2AuthenticationContext}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.2.0
|
||||
* @see OAuth2AuthenticationContext
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OAuth2AuthenticationValidator {
|
||||
|
||||
/**
|
||||
* Validate the attribute(s) of the {@link Authentication}.
|
||||
*
|
||||
* @param authenticationContext the authentication context
|
||||
* @throws OAuth2AuthenticationException if the attribute(s) of the {@code Authentication} is invalid
|
||||
*/
|
||||
void validate(OAuth2AuthenticationContext authenticationContext) throws OAuth2AuthenticationException;
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user