Compare commits
322 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28ac43bd50 | ||
|
|
5e684fedbe | ||
|
|
086a3b01a1 | ||
|
|
d10fe36ec0 | ||
|
|
cd2a990df5 | ||
|
|
dc77bcdd5a | ||
|
|
c0ac18d53a | ||
|
|
14cb58df2b | ||
|
|
2e2c9ea286 | ||
|
|
82e4f3a345 | ||
|
|
9b60ed23e1 | ||
|
|
332d1cc318 | ||
|
|
8defe2eb3a | ||
|
|
830f55e538 | ||
|
|
c418306fd9 | ||
|
|
9053e3188d | ||
|
|
f0b19f30d1 | ||
|
|
666d569b48 | ||
|
|
646ea00db2 | ||
|
|
d4357197c9 | ||
|
|
4ce999c014 | ||
|
|
5fa1e8e3b1 | ||
|
|
5982d2285c | ||
|
|
b455268fa1 | ||
|
|
25c4a7d541 | ||
|
|
088d9a8e34 | ||
|
|
defd1f90b8 | ||
|
|
c9954af084 | ||
|
|
dc0ca22f5e | ||
|
|
c7f01f0795 | ||
|
|
37e45619ae | ||
|
|
72c5e24ab8 | ||
|
|
8e8e6d1b17 | ||
|
|
9667229429 | ||
|
|
33bac0f7c2 | ||
|
|
63c248440c | ||
|
|
71be32b245 | ||
|
|
4b19637fbb | ||
|
|
26f15b99bb | ||
|
|
f3c17b3de0 | ||
|
|
298ebc7c01 | ||
|
|
0735abdaad | ||
|
|
0dfe5cb44a | ||
|
|
4ccdd2baf4 | ||
|
|
e4ce97b887 | ||
|
|
aaeca70b4c | ||
|
|
8e8979af60 | ||
|
|
725c300db2 | ||
|
|
f3f69b300f | ||
|
|
c926884049 | ||
|
|
0e99adc72e | ||
|
|
c444544a27 | ||
|
|
84c24b344f | ||
|
|
34d4131968 | ||
|
|
d3f25dd6ea | ||
|
|
f3c29bd545 | ||
|
|
ea1f95b4ed | ||
|
|
42d611828a | ||
|
|
9388002158 | ||
|
|
1d4dcddc11 | ||
|
|
3ee47efff7 | ||
|
|
fe27e39c5d | ||
|
|
57aadceb17 | ||
|
|
5484931892 | ||
|
|
b5db5ffe54 | ||
|
|
9312c1807b | ||
|
|
7680505eed | ||
|
|
d15a68514d | ||
|
|
53ed5b8481 | ||
|
|
c89f2f3819 | ||
|
|
ebecb2a7f6 | ||
|
|
4995acc825 | ||
|
|
a4a61fcf50 | ||
|
|
86997bc0ac | ||
|
|
a740e819ae | ||
|
|
c7815939d2 | ||
|
|
2c8d5a19ac | ||
|
|
6b5d9f0fe5 | ||
|
|
ea7c68997f | ||
|
|
115a78d5f5 | ||
|
|
1929e3a80a | ||
|
|
7546d18a40 | ||
|
|
83915e8421 | ||
|
|
0493bbf1d1 | ||
|
|
41f8c9cd00 | ||
|
|
3d4df8807d | ||
|
|
850bd76aee | ||
|
|
7f294abfbb | ||
|
|
3ea7d8c9b6 | ||
|
|
06ad211fce | ||
|
|
84e53f635c | ||
|
|
f6c4d49b9f | ||
|
|
06f2845ac0 | ||
|
|
a3b14a97d6 | ||
|
|
0723936b8a | ||
|
|
0d7727a7d4 | ||
|
|
79fd004346 | ||
|
|
70142f3705 | ||
|
|
c42f80c280 | ||
|
|
a7feab605b | ||
|
|
51966d52d5 | ||
|
|
20d47ecaa0 | ||
|
|
d85ce0a6dd | ||
|
|
5593208e61 | ||
|
|
beb1233358 | ||
|
|
75d649578a | ||
|
|
bd98031036 | ||
|
|
687f03f047 | ||
|
|
3dfcbfe136 | ||
|
|
2523916ca1 | ||
|
|
7ffcbe57a7 | ||
|
|
7b4fc46369 | ||
|
|
09439a10ce | ||
|
|
c93c1a8097 | ||
|
|
1ae4f7aa13 | ||
|
|
a11284f0f5 | ||
|
|
ad108f519a | ||
|
|
9787794ea1 | ||
|
|
41dd68903c | ||
|
|
f1834633b1 | ||
|
|
1e6032e01a | ||
|
|
059880f51a | ||
|
|
1d94ac85a4 | ||
|
|
d6ce4c237a | ||
|
|
4c314cf570 | ||
|
|
d520fa7774 | ||
|
|
65213e97d6 | ||
|
|
b921b85665 | ||
|
|
41c38a4b53 | ||
|
|
da945a12be | ||
|
|
51bf550d12 | ||
|
|
1f17005edd | ||
|
|
4204bc7e78 | ||
|
|
385fc37b1d | ||
|
|
b62b161b95 | ||
|
|
9def059e29 | ||
|
|
fb276e7a4a | ||
|
|
543fa264b3 | ||
|
|
cf235ceb4e | ||
|
|
4517022f36 | ||
|
|
08ba07d676 | ||
|
|
023e22c9d3 | ||
|
|
549cdc7222 | ||
|
|
4daade7366 | ||
|
|
a949998664 | ||
|
|
aa208a2d30 | ||
|
|
99fb4c8a5f | ||
|
|
5f994a83d8 | ||
|
|
623736d640 | ||
|
|
3318874da1 | ||
|
|
6f6829bb91 | ||
|
|
ab2f1749bf | ||
|
|
232b3b7ac6 | ||
|
|
67e62a2f21 | ||
|
|
473dedb9ad | ||
|
|
e5e391db38 | ||
|
|
4209ed7599 | ||
|
|
763ef2224b | ||
|
|
769cf8fac7 | ||
|
|
1f4b369912 | ||
|
|
7c97d8ede9 | ||
|
|
5a79234677 | ||
|
|
23732187a9 | ||
|
|
5e79f21913 | ||
|
|
5dbe973701 | ||
|
|
8e9563a440 | ||
|
|
566064ba9a | ||
|
|
7f095e0a6f | ||
|
|
552751bd93 | ||
|
|
f3cb8f758c | ||
|
|
683dad1443 | ||
|
|
4688b0f879 | ||
|
|
c37ecd747f | ||
|
|
6ddb73d54f | ||
|
|
5e0fe9c862 | ||
|
|
dcf8741d16 | ||
|
|
830556a19b | ||
|
|
e72899d827 | ||
|
|
ec09d120ef | ||
|
|
55dda0f767 | ||
|
|
7933cb8c4b | ||
|
|
93d16d4419 | ||
|
|
8cd954ffa2 | ||
|
|
0c70e8ad3a | ||
|
|
e7feb6c0ed | ||
|
|
8224a0d971 | ||
|
|
2712a7b86c | ||
|
|
7dc9da3340 | ||
|
|
0a4775423b | ||
|
|
a30a1692b2 | ||
|
|
77208f05d9 | ||
|
|
9a45ae9804 | ||
|
|
92e8c08ce6 | ||
|
|
4e2626e8b7 | ||
|
|
2d8d56840c | ||
|
|
85d6a12000 | ||
|
|
8d57c893fb | ||
|
|
658b186381 | ||
|
|
d33ec32017 | ||
|
|
59040a4c3d | ||
|
|
3b0938883b | ||
|
|
1962b9c5b7 | ||
|
|
a90d98aa1e | ||
|
|
e440935c14 | ||
|
|
1f9e0f56fb | ||
|
|
7ff237ed90 | ||
|
|
d6d74bc843 | ||
|
|
277565599a | ||
|
|
3b4cb382ec | ||
|
|
16c6ed4fb4 | ||
|
|
4cc1553542 | ||
|
|
4b45d69c17 | ||
|
|
4f3d7c4821 | ||
|
|
799d790423 | ||
|
|
9f1c760876 | ||
|
|
b25b906917 | ||
|
|
839123409d | ||
|
|
03d3879817 | ||
|
|
ef314e5c28 | ||
|
|
a7d4d45658 | ||
|
|
69a34bce5b | ||
|
|
7652d0ebbe | ||
|
|
b5d47366ad | ||
|
|
ece5f2b3b1 | ||
|
|
c00226d0c6 | ||
|
|
6ffda38cb9 | ||
|
|
09846eebeb | ||
|
|
c9afc3e061 | ||
|
|
afd5491ced | ||
|
|
313b4cc5d3 | ||
|
|
3c6571044d | ||
|
|
2f1684d44b | ||
|
|
2cdb7ef0fc | ||
|
|
ee1b46b9a6 | ||
|
|
cee5aacc15 | ||
|
|
fd9df9e2e7 | ||
|
|
7261b40cd5 | ||
|
|
41541912e6 | ||
|
|
bffcbc5440 | ||
|
|
218d49b134 | ||
|
|
1fa0161164 | ||
|
|
adf96b4e25 | ||
|
|
3f310eec00 | ||
|
|
aeab08579a | ||
|
|
8e5e5873f5 | ||
|
|
39ed820560 | ||
|
|
5b8d0c3301 | ||
|
|
698d45cdbd | ||
|
|
dc2fe30570 | ||
|
|
4bcc1afac7 | ||
|
|
17c20e98d4 | ||
|
|
b7996e26d0 | ||
|
|
12f4001c9d | ||
|
|
4b37606807 | ||
|
|
36e66bd732 | ||
|
|
42a89d15b1 | ||
|
|
259b55f682 | ||
|
|
b6932ed25e | ||
|
|
f9f15227d8 | ||
|
|
668bb069f2 | ||
|
|
f2bb523105 | ||
|
|
8c71e56350 | ||
|
|
7c7e664bb7 | ||
|
|
7fae37f0b5 | ||
|
|
7f8aff7982 | ||
|
|
f077337e43 | ||
|
|
79f1cf5a50 | ||
|
|
f97b8b2656 | ||
|
|
4e4656f7bb | ||
|
|
eb97e12f56 | ||
|
|
ab591dc39d | ||
|
|
6a5e277a11 | ||
|
|
43fbd9d345 | ||
|
|
d97235d0bb | ||
|
|
9f246fc304 | ||
|
|
6db194da27 | ||
|
|
f0ecb5b93f | ||
|
|
c1e9c1d76c | ||
|
|
a9423c6b13 | ||
|
|
8100568613 | ||
|
|
e1f491bd61 | ||
|
|
90fbbea126 | ||
|
|
d6fc405bb1 | ||
|
|
db4dd3f08e | ||
|
|
6e2f2fe8a4 | ||
|
|
061d9f8c18 | ||
|
|
59f77d034e | ||
|
|
bf24cfb19e | ||
|
|
77a9b2ebf3 | ||
|
|
d76d209124 | ||
|
|
58ad2d2c6c | ||
|
|
1a6b3e3e59 | ||
|
|
e61639bf7f | ||
|
|
7e2264204b | ||
|
|
43b44a1f77 | ||
|
|
19d6e97372 | ||
|
|
cff7b786de | ||
|
|
edf23562cb | ||
|
|
e7909d0cdd | ||
|
|
e49d4a79b4 | ||
|
|
7720e275e4 | ||
|
|
6a2c841d06 | ||
|
|
d7fe79d0ec | ||
|
|
40ca7a4654 | ||
|
|
06bf391bfa | ||
|
|
bfb5432b46 | ||
|
|
9818618ea3 | ||
|
|
cb09aef605 | ||
|
|
ebcdf7989d | ||
|
|
df8793c902 | ||
|
|
6c7486429c | ||
|
|
cf82c06502 | ||
|
|
a2167a5091 | ||
|
|
78d4bd0bad | ||
|
|
1ce77d3caa | ||
|
|
b7ddb837d6 | ||
|
|
dc94e5e161 | ||
|
|
18f8b3afaa | ||
|
|
601640e4fa | ||
|
|
af60f3d4d0 | ||
|
|
7b1b965c08 | ||
|
|
0c6b1251ce |
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -3,6 +3,3 @@ contact_links:
|
||||
- name: Community Support
|
||||
url: https://stackoverflow.com/questions/tagged/spring-security
|
||||
about: Please ask and answer questions on StackOverflow with the tag `spring-security`.
|
||||
- name: Security Issues
|
||||
url: https://pivotal.io/security#reporting
|
||||
about: Please report security vulnerabilities here.
|
||||
|
||||
@@ -3,7 +3,7 @@ name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
|
||||
|
||||
2
.github/workflows/pr-build-workflow.yml
vendored
2
.github/workflows/pr-build-workflow.yml
vendored
@@ -3,7 +3,7 @@ name: PR build
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
= Contributing to Spring Authorization Server
|
||||
|
||||
Spring Authorization Server is released under the Apache 2.0 license.
|
||||
If you would like to contribute something, or simply want to hack on the code this document should help you https://github.com/spring-projects-experimental/spring-authorization-server#getting-started[get started].
|
||||
If you would like to contribute something, or simply want to hack on the code this document should help you https://github.com/spring-projects/spring-authorization-server#getting-started[get started].
|
||||
|
||||
== Code of Conduct
|
||||
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].
|
||||
@@ -32,7 +32,7 @@ That may mean using an external library directly in a `Filter`.
|
||||
|
||||
== Reporting Security Vulnerabilities
|
||||
If you think you have found a security vulnerability please *DO NOT* disclose it publicly until we've had a chance to fix it.
|
||||
Please don't report security vulnerabilities using GitHub issues, instead head over to https://pivotal.io/security and learn how to disclose them responsibly.
|
||||
Please don't report security vulnerabilities using GitHub issues, instead head over to https://spring.io/security-policy and learn how to disclose them responsibly.
|
||||
|
||||
== Sign the Contributor License Agreement
|
||||
Before we accept a non-trivial patch or pull request we will need you to https://cla.pivotal.io/sign/spring[sign the Contributor License Agreement].
|
||||
@@ -45,7 +45,7 @@ Please add the Apache License header to all new classes, for example:
|
||||
|
||||
```java
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* 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.
|
||||
|
||||
12
Jenkinsfile
vendored
12
Jenkinsfile
vendored
@@ -15,10 +15,11 @@ def GRADLE_ENTERPRISE_SECRET_ACCESS_KEY = string(credentialsId: 'gradle_enterpri
|
||||
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_CREDENTIALS = usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')
|
||||
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)
|
||||
@@ -32,13 +33,14 @@ try {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withCredentials([GRADLE_ENTERPRISE_CACHE_USER,
|
||||
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 check --stacktrace"
|
||||
sh "./gradlew $JENKINS_USER check -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --stacktrace"
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
@@ -59,7 +61,7 @@ try {
|
||||
sh "git clean -dfx"
|
||||
withCredentials([SPRING_SIGNING_SECRING,
|
||||
SPRING_GPG_PASSPHRASE,
|
||||
OSSRH_CREDENTIALS,
|
||||
OSSRH_S01_CREDENTIALS,
|
||||
ARTIFACTORY_CREDENTIALS,
|
||||
GRADLE_ENTERPRISE_CACHE_USER,
|
||||
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY]) {
|
||||
@@ -67,7 +69,7 @@ try {
|
||||
"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 deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --stacktrace"
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
33
README.adoc
33
README.adoc
@@ -1,32 +1,33 @@
|
||||
image::https://badges.gitter.im/Join%20Chat.svg[Gitter,link=https://gitter.im/spring-projects/spring-security?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge]
|
||||
|
||||
image:https://github.com/spring-projects-experimental/spring-authorization-server/workflows/CI/badge.svg?branch=master["Build Status", link="https://github.com/spring-projects-experimental/spring-authorization-server/actions?query=workflow%3ACI"]
|
||||
image:https://github.com/spring-projects/spring-authorization-server/workflows/CI/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects/spring-authorization-server/actions?query=workflow%3ACI"]
|
||||
|
||||
= Spring Authorization Server
|
||||
|
||||
Spring Authorization Server is a community-driven project led by the https://spring.io/projects/spring-security/[Spring Security] team and is focused on delivering https://tools.ietf.org/html/rfc6749#section-1.1[OAuth 2.0 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-01#section-1.1[OAuth 2.1 Authorization Server] support to the Spring community.
|
||||
|
||||
The project will start in Spring's experimental projects as an independent project so that it can evolve more rapidly.
|
||||
|
||||
The ultimate goal of this project is to replace the Authorization Server support provided by https://spring.io/projects/spring-security-oauth/[Spring Security OAuth].
|
||||
|
||||
With the much needed help from our community, this project will grow in the same way that the original Spring Security OAuth project did.
|
||||
This project replaces the Authorization Server support provided by https://spring.io/projects/spring-security-oauth/[Spring Security OAuth].
|
||||
|
||||
== Feature Planning
|
||||
This project uses https://www.zenhub.com/[ZenHub] to prioritize the feature roadmap and help organize the project plan.
|
||||
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].
|
||||
|
||||
== Support Policy
|
||||
The Spring Authorization Server project provides software support and is documented in its link:SUPPORT_POLICY.adoc[support policy].
|
||||
|
||||
== Getting Started
|
||||
The first place to start is to read the https://tools.ietf.org/html/rfc6749[OAuth 2.0 Authorization Framework] to gain an in-depth understanding on how to build an Authorization Server.
|
||||
It is a critically important first step as the implementation must conform to the specification defined in the OAuth 2.0 Authorization Framework and the https://github.com/spring-projects-experimental/spring-authorization-server/wiki/OAuth-2.0-Specifications[related specifications].
|
||||
The first place to start is to read the https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01[OAuth 2.1 Authorization Framework] to gain an in-depth understanding on how to build an Authorization Server.
|
||||
It is a critically important first step as the implementation must conform to the specification defined in the OAuth 2.1 Authorization Framework and the https://github.com/spring-projects/spring-authorization-server/wiki/OAuth-2.0-Specifications[related specifications].
|
||||
|
||||
The second place to start is to become very familiar with the codebase in the following Spring Security modules:
|
||||
|
||||
- https://github.com/spring-projects/spring-security/tree/master/oauth2/oauth2-core[OAuth 2.0 Core]
|
||||
- https://github.com/spring-projects/spring-security/tree/master/oauth2/oauth2-client[OAuth 2.0 Client]
|
||||
- https://github.com/spring-projects/spring-security/tree/master/oauth2/oauth2-resource-server[OAuth 2.0 Resource Server]
|
||||
- https://github.com/spring-projects/spring-security/tree/master/oauth2/oauth2-jose[OAuth 2.0 JOSE] (Javascript Object Signing and Encryption)
|
||||
- https://github.com/spring-projects/spring-security/tree/main/oauth2/oauth2-core[OAuth 2.0 Core]
|
||||
- https://github.com/spring-projects/spring-security/tree/main/oauth2/oauth2-client[OAuth 2.0 Client]
|
||||
- https://github.com/spring-projects/spring-security/tree/main/oauth2/oauth2-resource-server[OAuth 2.0 Resource Server]
|
||||
- https://github.com/spring-projects/spring-security/tree/main/oauth2/oauth2-jose[OAuth 2.0 JOSE] (Javascript Object Signing and Encryption)
|
||||
|
||||
A significant amount of effort was put into developing the https://spring.io/blog/2018/01/30/next-generation-oauth-2-0-support-with-spring-security[Next Generation OAuth 2.0 Support in Spring Security].
|
||||
The goal is to leverage all the knowledge learned thus far and apply the same to the development of Spring Authorization Server.
|
||||
@@ -34,7 +35,7 @@ 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/site/docs/current/reference/html5/[Spring Security Reference], as well as the https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2[OAuth 2.0 Reference], which describes the Client and Resource Server features available.
|
||||
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.
|
||||
|
||||
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].
|
||||
|
||||
@@ -43,7 +44,7 @@ This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code
|
||||
By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
== Downloading Artifacts
|
||||
See https://github.com/spring-projects/spring-framework/wiki/Downloading-Spring-artifacts[downloading Spring artifacts] for Maven repository information.
|
||||
See https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Artifacts[downloading Spring artifacts] for Maven repository information.
|
||||
|
||||
== Building from Source
|
||||
Spring Authorization Server uses a https://gradle.org[Gradle]-based build system.
|
||||
@@ -58,7 +59,7 @@ Be sure that your `JAVA_HOME` environment variable points to the `jdk1.8.0` fold
|
||||
=== Check out sources
|
||||
[indent=0]
|
||||
----
|
||||
git clone git@github.com:spring-projects-experimental/spring-authorization-server.git
|
||||
git clone git@github.com:spring-projects/spring-authorization-server.git
|
||||
|
||||
----
|
||||
|
||||
|
||||
21
SUPPORT_POLICY.adoc
Normal file
21
SUPPORT_POLICY.adoc
Normal file
@@ -0,0 +1,21 @@
|
||||
= Spring Authorization Server Support Policy
|
||||
|
||||
The Spring Authorization Server support offering provides the following support terms:
|
||||
|
||||
* Releases are currently in the format of 0.x.y, where:
|
||||
** “x” contains new features and potentially breaking changes.
|
||||
** “y” contains new features and bug fixes and provides backward compatibility.
|
||||
* The Spring Authorization Server project will be supported for at least 3 years after the most recent 0.x.0 release is made available for download.
|
||||
* Security fixes will be provided for at least one year after the 0.x.0 release is made available for download. Security fixes will not be provided for updating versions to third-party libraries.
|
||||
* Feature support and bug fixes, excluding “Security fixes”, will be provided only for the latest 0.x.y release.
|
||||
* This support policy starts with version 0.2.0.
|
||||
* We will switch to the standard https://tanzu.vmware.com/support/oss[Spring OSS support policy] when the Spring Authorization Server project reaches version 1.0.0.
|
||||
|
||||
An example can help us understand all of these points.
|
||||
Assume that 0.2.0 is released in August of 2021.
|
||||
This means that the Spring Authorization Server project is supported until at least August of 2024.
|
||||
If 0.3.0 is then released in May of 2022, the Spring Authorization Server project is supported until at least May of 2025.
|
||||
The 0.3.0 release may contain breaking changes from 0.2.0.
|
||||
If a bug is found, only 0.3.0 will be patched in a 0.3.1 release.
|
||||
If a security vulnerability is found, a 0.2.4 (assume 0.2.3 is latest) and 0.3.1 release will be provided to fix the security vulnerability.
|
||||
However, a vulnerability found in September of 2022 would be fixed in the 0.3.1 release but not the 0.2.3 release, because the vulnerability was discovered more than a year after the 0.2.0 release date.
|
||||
16
build.gradle
16
build.gradle
@@ -1,11 +1,19 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath 'io.spring.gradle:spring-build-conventions:0.0.33.RELEASE'
|
||||
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.5.RELEASE'
|
||||
classpath 'io.spring.nohttp:nohttp-gradle:0.0.8'
|
||||
}
|
||||
repositories {
|
||||
maven { url 'https://repo.spring.io/plugins-snapshot' }
|
||||
maven {
|
||||
url = 'https://repo.spring.io/plugins-snapshot'
|
||||
if (project.hasProperty('artifactoryUsername')) {
|
||||
credentials {
|
||||
username "$artifactoryUsername"
|
||||
password "$artifactoryPassword"
|
||||
}
|
||||
}
|
||||
}
|
||||
maven { url 'https://plugins.gradle.org/m2/' }
|
||||
}
|
||||
}
|
||||
@@ -14,7 +22,7 @@ apply plugin: 'io.spring.nohttp'
|
||||
apply plugin: 'locks'
|
||||
apply plugin: 'io.spring.convention.root'
|
||||
|
||||
group = 'org.springframework.security.experimental'
|
||||
group = 'org.springframework.security'
|
||||
description = 'Spring Authorization Server'
|
||||
|
||||
ext.snapshotBuild = version.contains("SNAPSHOT")
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,4 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.4.2.RELEASE
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,4 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
io.spring.docresources:spring-doc-resources:0.2.1.RELEASE
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,13 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
junit:junit:4.13.1
|
||||
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||
net.bytebuddy:byte-buddy:1.10.15
|
||||
org.assertj:assertj-core:3.17.2
|
||||
org.hamcrest:hamcrest-core:1.3
|
||||
org.mockito:mockito-core:3.5.13
|
||||
org.objenesis:objenesis:3.1
|
||||
org.springframework:spring-core:5.2.9.RELEASE
|
||||
org.springframework:spring-jcl:5.2.9.RELEASE
|
||||
org.springframework:spring-test:5.2.9.RELEASE
|
||||
@@ -1,13 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
junit:junit:4.13.1
|
||||
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||
net.bytebuddy:byte-buddy:1.10.15
|
||||
org.assertj:assertj-core:3.17.2
|
||||
org.hamcrest:hamcrest-core:1.3
|
||||
org.mockito:mockito-core:3.5.13
|
||||
org.objenesis:objenesis:3.1
|
||||
org.springframework:spring-core:5.2.9.RELEASE
|
||||
org.springframework:spring-jcl:5.2.9.RELEASE
|
||||
org.springframework:spring-test:5.2.9.RELEASE
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,13 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
junit:junit:4.13.1
|
||||
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||
net.bytebuddy:byte-buddy:1.10.15
|
||||
org.assertj:assertj-core:3.17.2
|
||||
org.hamcrest:hamcrest-core:1.3
|
||||
org.mockito:mockito-core:3.5.13
|
||||
org.objenesis:objenesis:3.1
|
||||
org.springframework:spring-core:5.2.9.RELEASE
|
||||
org.springframework:spring-jcl:5.2.9.RELEASE
|
||||
org.springframework:spring-test:5.2.9.RELEASE
|
||||
@@ -1,13 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
junit:junit:4.13.1
|
||||
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||
net.bytebuddy:byte-buddy:1.10.15
|
||||
org.assertj:assertj-core:3.17.2
|
||||
org.hamcrest:hamcrest-core:1.3
|
||||
org.mockito:mockito-core:3.5.13
|
||||
org.objenesis:objenesis:3.1
|
||||
org.springframework:spring-core:5.2.9.RELEASE
|
||||
org.springframework:spring-jcl:5.2.9.RELEASE
|
||||
org.springframework:spring-test:5.2.9.RELEASE
|
||||
@@ -14,8 +14,8 @@ asciidoctor {
|
||||
}
|
||||
|
||||
asciidoctorj {
|
||||
def ghTag = snapshotBuild ? 'master' : project.version
|
||||
def ghUrl = "https://github.com/spring-projects-experimental/spring-authorization-server/tree/$ghTag"
|
||||
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,
|
||||
@@ -31,3 +31,7 @@ def resolvedVersions(Configuration configuration) {
|
||||
.collectEntries { [(it.name + "-version"): it.moduleVersion.id.version] }
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url "https://repo.spring.io/release" }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
version=0.0.2
|
||||
springBootVersion=2.4.0-M3
|
||||
version=0.2.1
|
||||
springBootVersion=2.5.7
|
||||
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
if (!project.hasProperty("springVersion")) {
|
||||
ext.springVersion = "5.2.+"
|
||||
ext.springVersion = "5.3.13"
|
||||
}
|
||||
|
||||
if (!project.hasProperty("springSecurityVersion")) {
|
||||
ext.springSecurityVersion = "5.4.+"
|
||||
ext.springSecurityVersion = "5.5.3"
|
||||
}
|
||||
|
||||
if (!project.hasProperty("reactorVersion")) {
|
||||
ext.reactorVersion = "Dysprosium-SR+"
|
||||
ext.reactorVersion = "2020.0.13"
|
||||
}
|
||||
|
||||
if (!project.hasProperty("locksDisabled")) {
|
||||
@@ -21,47 +21,18 @@ dependencyManagement {
|
||||
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.+"
|
||||
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.5"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
dependency "com.nimbusds:oauth2-oidc-sdk:latest.release"
|
||||
dependency "com.nimbusds:nimbus-jose-jwt:latest.release"
|
||||
dependency "javax.servlet:javax.servlet-api:4.+"
|
||||
dependency 'junit:junit:latest.release'
|
||||
dependency 'org.assertj:assertj-core:latest.release'
|
||||
dependency 'org.mockito:mockito-core:latest.release'
|
||||
dependency "com.squareup.okhttp3:mockwebserver:3.+"
|
||||
dependency "com.squareup.okhttp3:okhttp:3.+"
|
||||
dependency "com.jayway.jsonpath:json-path:2.+"
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
NOTE:
|
||||
The latest `reactor-netty` dependency was split into `reactor-netty-core` and `reactor-netty-http`,
|
||||
which resulted in the snapshot build to fail. The below configuration fixes it.
|
||||
|
||||
Reference:
|
||||
- https://github.com/spring-projects/spring-security/issues/8909
|
||||
- https://github.com/reactor/reactor-netty/issues/739#issuecomment-667047117
|
||||
*/
|
||||
if (reactorVersion.startsWith('20')) {
|
||||
if (reactorVersion.endsWith('SNAPSHOT') || reactorVersion.endsWith('+')) {
|
||||
ext.reactorLatestVersion = "latest.integration"
|
||||
} else {
|
||||
ext.reactorLatestVersion = "latest.release"
|
||||
}
|
||||
configurations {
|
||||
all {
|
||||
resolutionStrategy {
|
||||
eachDependency { DependencyResolveDetails details ->
|
||||
if (details.requested.name == 'reactor-netty') {
|
||||
details.useTarget("${details.requested.group}:reactor-netty-http:${reactorLatestVersion}")
|
||||
details.because("reactor-netty is now split into reactor-netty-core and reactor-netty-http")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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.4.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,23 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
antlr:antlr:2.7.7
|
||||
ch.qos.logback:logback-classic:1.2.3
|
||||
ch.qos.logback:logback-core:1.2.3
|
||||
com.google.code.findbugs:jsr305:3.0.2
|
||||
com.google.errorprone:error_prone_annotations:2.2.0
|
||||
com.google.guava:failureaccess:1.0.1
|
||||
com.google.guava:guava:27.1-jre
|
||||
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
|
||||
com.google.j2objc:j2objc-annotations:1.1
|
||||
com.puppycrawl.tools:checkstyle:8.22
|
||||
commons-beanutils:commons-beanutils:1.9.3
|
||||
commons-collections:commons-collections:3.2.2
|
||||
info.picocli:picocli:3.9.6
|
||||
io.spring.javaformat:spring-javaformat-checkstyle:0.0.15
|
||||
io.spring.nohttp:nohttp-checkstyle:0.0.3.RELEASE
|
||||
io.spring.nohttp:nohttp:0.0.3.RELEASE
|
||||
net.sf.saxon:Saxon-HE:9.9.1-3
|
||||
org.antlr:antlr4-runtime:4.7.2
|
||||
org.checkerframework:checker-qual:2.5.2
|
||||
org.codehaus.mojo:animal-sniffer-annotations:1.17
|
||||
@@ -1,21 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||
com.nimbusds:nimbus-jose-jwt:9.0.1
|
||||
org.springframework.security:spring-security-config:5.4.1
|
||||
org.springframework.security:spring-security-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||
org.springframework.security:spring-security-web:5.4.1
|
||||
org.springframework:spring-aop:5.2.9.RELEASE
|
||||
org.springframework:spring-beans:5.2.9.RELEASE
|
||||
org.springframework:spring-context:5.2.9.RELEASE
|
||||
org.springframework:spring-core:5.2.9.RELEASE
|
||||
org.springframework:spring-expression:5.2.9.RELEASE
|
||||
org.springframework:spring-jcl:5.2.9.RELEASE
|
||||
org.springframework:spring-web:5.2.9.RELEASE
|
||||
@@ -1,21 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||
com.nimbusds:nimbus-jose-jwt:9.0.1
|
||||
org.springframework.security:spring-security-config:5.4.1
|
||||
org.springframework.security:spring-security-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||
org.springframework.security:spring-security-web:5.4.1
|
||||
org.springframework:spring-aop:5.2.9.RELEASE
|
||||
org.springframework:spring-beans:5.2.9.RELEASE
|
||||
org.springframework:spring-context:5.2.9.RELEASE
|
||||
org.springframework:spring-core:5.2.9.RELEASE
|
||||
org.springframework:spring-expression:5.2.9.RELEASE
|
||||
org.springframework:spring-jcl:5.2.9.RELEASE
|
||||
org.springframework:spring-web:5.2.9.RELEASE
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,21 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||
com.nimbusds:nimbus-jose-jwt:9.0.1
|
||||
org.springframework.security:spring-security-config:5.4.1
|
||||
org.springframework.security:spring-security-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||
org.springframework.security:spring-security-web:5.4.1
|
||||
org.springframework:spring-aop:5.2.9.RELEASE
|
||||
org.springframework:spring-beans:5.2.9.RELEASE
|
||||
org.springframework:spring-context:5.2.9.RELEASE
|
||||
org.springframework:spring-core:5.2.9.RELEASE
|
||||
org.springframework:spring-expression:5.2.9.RELEASE
|
||||
org.springframework:spring-jcl:5.2.9.RELEASE
|
||||
org.springframework:spring-web:5.2.9.RELEASE
|
||||
@@ -1,4 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
org.jacoco:org.jacoco.agent:0.8.5
|
||||
@@ -1,11 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
org.jacoco:org.jacoco.agent:0.8.5
|
||||
org.jacoco:org.jacoco.ant:0.8.5
|
||||
org.jacoco:org.jacoco.core:0.8.5
|
||||
org.jacoco:org.jacoco.report:0.8.5
|
||||
org.ow2.asm:asm-analysis:7.2
|
||||
org.ow2.asm:asm-commons:7.2
|
||||
org.ow2.asm:asm-tree:7.2
|
||||
org.ow2.asm:asm:7.2
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,4 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
javax.servlet:javax.servlet-api:4.0.1
|
||||
@@ -1,21 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||
com.nimbusds:nimbus-jose-jwt:9.0.1
|
||||
org.springframework.security:spring-security-config:5.4.1
|
||||
org.springframework.security:spring-security-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||
org.springframework.security:spring-security-web:5.4.1
|
||||
org.springframework:spring-aop:5.2.9.RELEASE
|
||||
org.springframework:spring-beans:5.2.9.RELEASE
|
||||
org.springframework:spring-context:5.2.9.RELEASE
|
||||
org.springframework:spring-core:5.2.9.RELEASE
|
||||
org.springframework:spring-expression:5.2.9.RELEASE
|
||||
org.springframework:spring-jcl:5.2.9.RELEASE
|
||||
org.springframework:spring-web:5.2.9.RELEASE
|
||||
@@ -1,21 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||
com.nimbusds:nimbus-jose-jwt:9.0.1
|
||||
org.springframework.security:spring-security-config:5.4.1
|
||||
org.springframework.security:spring-security-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||
org.springframework.security:spring-security-web:5.4.1
|
||||
org.springframework:spring-aop:5.2.9.RELEASE
|
||||
org.springframework:spring-beans:5.2.9.RELEASE
|
||||
org.springframework:spring-context:5.2.9.RELEASE
|
||||
org.springframework:spring-core:5.2.9.RELEASE
|
||||
org.springframework:spring-expression:5.2.9.RELEASE
|
||||
org.springframework:spring-jcl:5.2.9.RELEASE
|
||||
org.springframework:spring-web:5.2.9.RELEASE
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,36 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||
com.jayway.jsonpath:json-path:2.4.0
|
||||
com.nimbusds:nimbus-jose-jwt:9.0.1
|
||||
junit:junit:4.13.1
|
||||
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||
net.bytebuddy:byte-buddy:1.10.15
|
||||
net.minidev:accessors-smart:1.2
|
||||
net.minidev:json-smart:2.3
|
||||
org.assertj:assertj-core:3.17.2
|
||||
org.hamcrest:hamcrest-core:1.3
|
||||
org.mockito:mockito-core:3.5.13
|
||||
org.objenesis:objenesis:3.1
|
||||
org.ow2.asm:asm:5.0.4
|
||||
org.slf4j:slf4j-api:1.7.25
|
||||
org.springframework.security:spring-security-config:5.4.1
|
||||
org.springframework.security:spring-security-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||
org.springframework.security:spring-security-test:5.4.1
|
||||
org.springframework.security:spring-security-web:5.4.1
|
||||
org.springframework:spring-aop:5.2.9.RELEASE
|
||||
org.springframework:spring-beans:5.2.9.RELEASE
|
||||
org.springframework:spring-context:5.2.9.RELEASE
|
||||
org.springframework:spring-core:5.2.9.RELEASE
|
||||
org.springframework:spring-expression:5.2.9.RELEASE
|
||||
org.springframework:spring-jcl:5.2.9.RELEASE
|
||||
org.springframework:spring-test:5.2.9.RELEASE
|
||||
org.springframework:spring-web:5.2.9.RELEASE
|
||||
org.springframework:spring-webmvc:5.2.9.RELEASE
|
||||
@@ -1,36 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||
com.jayway.jsonpath:json-path:2.4.0
|
||||
com.nimbusds:nimbus-jose-jwt:9.0.1
|
||||
junit:junit:4.13.1
|
||||
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||
net.bytebuddy:byte-buddy:1.10.15
|
||||
net.minidev:accessors-smart:1.2
|
||||
net.minidev:json-smart:2.3
|
||||
org.assertj:assertj-core:3.17.2
|
||||
org.hamcrest:hamcrest-core:1.3
|
||||
org.mockito:mockito-core:3.5.13
|
||||
org.objenesis:objenesis:3.1
|
||||
org.ow2.asm:asm:5.0.4
|
||||
org.slf4j:slf4j-api:1.7.25
|
||||
org.springframework.security:spring-security-config:5.4.1
|
||||
org.springframework.security:spring-security-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||
org.springframework.security:spring-security-test:5.4.1
|
||||
org.springframework.security:spring-security-web:5.4.1
|
||||
org.springframework:spring-aop:5.2.9.RELEASE
|
||||
org.springframework:spring-beans:5.2.9.RELEASE
|
||||
org.springframework:spring-context:5.2.9.RELEASE
|
||||
org.springframework:spring-core:5.2.9.RELEASE
|
||||
org.springframework:spring-expression:5.2.9.RELEASE
|
||||
org.springframework:spring-jcl:5.2.9.RELEASE
|
||||
org.springframework:spring-test:5.2.9.RELEASE
|
||||
org.springframework:spring-web:5.2.9.RELEASE
|
||||
org.springframework:spring-webmvc:5.2.9.RELEASE
|
||||
@@ -1,3 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
@@ -1,36 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||
com.jayway.jsonpath:json-path:2.4.0
|
||||
com.nimbusds:nimbus-jose-jwt:9.0.1
|
||||
junit:junit:4.13.1
|
||||
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||
net.bytebuddy:byte-buddy:1.10.15
|
||||
net.minidev:accessors-smart:1.2
|
||||
net.minidev:json-smart:2.3
|
||||
org.assertj:assertj-core:3.17.2
|
||||
org.hamcrest:hamcrest-core:1.3
|
||||
org.mockito:mockito-core:3.5.13
|
||||
org.objenesis:objenesis:3.1
|
||||
org.ow2.asm:asm:5.0.4
|
||||
org.slf4j:slf4j-api:1.7.25
|
||||
org.springframework.security:spring-security-config:5.4.1
|
||||
org.springframework.security:spring-security-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||
org.springframework.security:spring-security-test:5.4.1
|
||||
org.springframework.security:spring-security-web:5.4.1
|
||||
org.springframework:spring-aop:5.2.9.RELEASE
|
||||
org.springframework:spring-beans:5.2.9.RELEASE
|
||||
org.springframework:spring-context:5.2.9.RELEASE
|
||||
org.springframework:spring-core:5.2.9.RELEASE
|
||||
org.springframework:spring-expression:5.2.9.RELEASE
|
||||
org.springframework:spring-jcl:5.2.9.RELEASE
|
||||
org.springframework:spring-test:5.2.9.RELEASE
|
||||
org.springframework:spring-web:5.2.9.RELEASE
|
||||
org.springframework:spring-webmvc:5.2.9.RELEASE
|
||||
@@ -1,36 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||
com.jayway.jsonpath:json-path:2.4.0
|
||||
com.nimbusds:nimbus-jose-jwt:9.0.1
|
||||
junit:junit:4.13.1
|
||||
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||
net.bytebuddy:byte-buddy:1.10.15
|
||||
net.minidev:accessors-smart:1.2
|
||||
net.minidev:json-smart:2.3
|
||||
org.assertj:assertj-core:3.17.2
|
||||
org.hamcrest:hamcrest-core:1.3
|
||||
org.mockito:mockito-core:3.5.13
|
||||
org.objenesis:objenesis:3.1
|
||||
org.ow2.asm:asm:5.0.4
|
||||
org.slf4j:slf4j-api:1.7.25
|
||||
org.springframework.security:spring-security-config:5.4.1
|
||||
org.springframework.security:spring-security-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||
org.springframework.security:spring-security-test:5.4.1
|
||||
org.springframework.security:spring-security-web:5.4.1
|
||||
org.springframework:spring-aop:5.2.9.RELEASE
|
||||
org.springframework:spring-beans:5.2.9.RELEASE
|
||||
org.springframework:spring-context:5.2.9.RELEASE
|
||||
org.springframework:spring-core:5.2.9.RELEASE
|
||||
org.springframework:spring-expression:5.2.9.RELEASE
|
||||
org.springframework:spring-jcl:5.2.9.RELEASE
|
||||
org.springframework:spring-test:5.2.9.RELEASE
|
||||
org.springframework:spring-web:5.2.9.RELEASE
|
||||
org.springframework:spring-webmvc:5.2.9.RELEASE
|
||||
@@ -1,36 +0,0 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
|
||||
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
|
||||
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
|
||||
com.github.stephenc.jcip:jcip-annotations:1.0-1
|
||||
com.jayway.jsonpath:json-path:2.4.0
|
||||
com.nimbusds:nimbus-jose-jwt:9.0.1
|
||||
junit:junit:4.13.1
|
||||
net.bytebuddy:byte-buddy-agent:1.10.15
|
||||
net.bytebuddy:byte-buddy:1.10.15
|
||||
net.minidev:accessors-smart:1.2
|
||||
net.minidev:json-smart:2.3
|
||||
org.assertj:assertj-core:3.17.2
|
||||
org.hamcrest:hamcrest-core:1.3
|
||||
org.mockito:mockito-core:3.5.13
|
||||
org.objenesis:objenesis:3.1
|
||||
org.ow2.asm:asm:5.0.4
|
||||
org.slf4j:slf4j-api:1.7.25
|
||||
org.springframework.security:spring-security-config:5.4.1
|
||||
org.springframework.security:spring-security-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-core:5.4.1
|
||||
org.springframework.security:spring-security-oauth2-jose:5.4.1
|
||||
org.springframework.security:spring-security-test:5.4.1
|
||||
org.springframework.security:spring-security-web:5.4.1
|
||||
org.springframework:spring-aop:5.2.9.RELEASE
|
||||
org.springframework:spring-beans:5.2.9.RELEASE
|
||||
org.springframework:spring-context:5.2.9.RELEASE
|
||||
org.springframework:spring-core:5.2.9.RELEASE
|
||||
org.springframework:spring-expression:5.2.9.RELEASE
|
||||
org.springframework:spring-jcl:5.2.9.RELEASE
|
||||
org.springframework:spring-test:5.2.9.RELEASE
|
||||
org.springframework:spring-web:5.2.9.RELEASE
|
||||
org.springframework:spring-webmvc:5.2.9.RELEASE
|
||||
@@ -5,10 +5,14 @@ dependencies {
|
||||
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'
|
||||
|
||||
optional 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
|
||||
optional 'org.springframework:spring-jdbc'
|
||||
|
||||
testCompile 'org.springframework.security:spring-security-test'
|
||||
testCompile 'org.springframework:spring-webmvc'
|
||||
testCompile 'junit:junit'
|
||||
@@ -16,9 +20,11 @@ dependencies {
|
||||
testCompile 'org.mockito:mockito-core'
|
||||
testCompile 'com.jayway.jsonpath:json-path'
|
||||
|
||||
testRuntime 'org.hsqldb:hsqldb'
|
||||
|
||||
provided 'javax.servlet:javax.servlet-api'
|
||||
}
|
||||
|
||||
jacoco {
|
||||
toolVersion = '0.8.5'
|
||||
toolVersion = '0.8.6'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -15,23 +15,83 @@
|
||||
*/
|
||||
package org.springframework.security.config.annotation.web.configuration;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.JWSKeySelector;
|
||||
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
|
||||
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* {@link Configuration} for OAuth 2.0 Authorization Server support.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
* @see OAuth2AuthorizationServerConfigurer
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class OAuth2AuthorizationServerConfiguration {
|
||||
|
||||
@Bean
|
||||
public WebSecurityConfigurer<WebSecurity> defaultOAuth2AuthorizationServerSecurity() {
|
||||
return new OAuth2AuthorizationServerSecurity();
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
applyDefaultSecurity(http);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
public static void applyDefaultSecurity(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer<>();
|
||||
RequestMatcher endpointsMatcher = authorizationServerConfigurer
|
||||
.getEndpointsMatcher();
|
||||
|
||||
http
|
||||
.requestMatcher(endpointsMatcher)
|
||||
.authorizeRequests(authorizeRequests ->
|
||||
authorizeRequests.anyRequest().authenticated()
|
||||
)
|
||||
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
|
||||
.apply(authorizationServerConfigurer);
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
public static JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
|
||||
Set<JWSAlgorithm> jwsAlgs = new HashSet<>();
|
||||
jwsAlgs.addAll(JWSAlgorithm.Family.RSA);
|
||||
jwsAlgs.addAll(JWSAlgorithm.Family.EC);
|
||||
jwsAlgs.addAll(JWSAlgorithm.Family.HMAC_SHA);
|
||||
ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
|
||||
JWSKeySelector<SecurityContext> jwsKeySelector =
|
||||
new JWSVerificationKeySelector<>(jwsAlgs, jwkSource);
|
||||
jwtProcessor.setJWSKeySelector(jwsKeySelector);
|
||||
// Override the default Nimbus claims set verifier as NimbusJwtDecoder handles it instead
|
||||
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
|
||||
});
|
||||
return new NimbusJwtDecoder(jwtProcessor);
|
||||
}
|
||||
|
||||
@Bean
|
||||
RegisterMissingBeanPostProcessor registerMissingBeanPostProcessor() {
|
||||
RegisterMissingBeanPostProcessor postProcessor = new RegisterMissingBeanPostProcessor();
|
||||
postProcessor.addBeanDefinition(ProviderSettings.class, () -> ProviderSettings.builder().build());
|
||||
return postProcessor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.config.annotation.web.configuration;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
* {@link WebSecurityConfigurerAdapter} providing default security configuration for OAuth 2.0 Authorization Server.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
*/
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class OAuth2AuthorizationServerSecurity extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
applyDefaultConfiguration(http);
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
public static void applyDefaultConfiguration(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer<>();
|
||||
RequestMatcher[] endpointMatchers = authorizationServerConfigurer
|
||||
.getEndpointMatchers().toArray(new RequestMatcher[0]);
|
||||
|
||||
http
|
||||
.requestMatcher(new OrRequestMatcher(endpointMatchers))
|
||||
.authorizeRequests(authorizeRequests ->
|
||||
authorizeRequests.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(withDefaults())
|
||||
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointMatchers))
|
||||
.apply(authorizationServerConfigurer);
|
||||
}
|
||||
// @formatter:on
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
|
||||
|
||||
/**
|
||||
* Post processor to register one or more bean definitions on container initialization, if not already present.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
* @since 0.2.0
|
||||
*/
|
||||
final class RegisterMissingBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
|
||||
private final AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
|
||||
private final List<AbstractBeanDefinition> beanDefinitions = new ArrayList<>();
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
for (AbstractBeanDefinition beanDefinition : this.beanDefinitions) {
|
||||
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
|
||||
(ListableBeanFactory) this.beanFactory, beanDefinition.getBeanClass(), false, false);
|
||||
if (beanNames.length == 0) {
|
||||
String beanName = this.beanNameGenerator.generateBeanName(beanDefinition, registry);
|
||||
registry.registerBeanDefinition(beanName, beanDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
}
|
||||
|
||||
<T> void addBeanDefinition(Class<T> beanClass, Supplier<T> beanSupplier) {
|
||||
this.beanDefinitions.add(new RootBeanDefinition(beanClass, beanSupplier));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* Base configurer for an OAuth 2.0 component (e.g. protocol endpoint).
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.2
|
||||
*/
|
||||
abstract class AbstractOAuth2Configurer {
|
||||
private final ObjectPostProcessor<Object> objectPostProcessor;
|
||||
|
||||
AbstractOAuth2Configurer(ObjectPostProcessor<Object> objectPostProcessor) {
|
||||
this.objectPostProcessor = objectPostProcessor;
|
||||
}
|
||||
|
||||
abstract <B extends HttpSecurityBuilder<B>> void init(B builder);
|
||||
|
||||
abstract <B extends HttpSecurityBuilder<B>> void configure(B builder);
|
||||
|
||||
abstract RequestMatcher getRequestMatcher();
|
||||
|
||||
protected final <T> T postProcess(T object) {
|
||||
return (T) this.objectPostProcessor.postProcess(object);
|
||||
}
|
||||
|
||||
protected final ObjectPostProcessor<Object> getObjectPostProcessor() {
|
||||
return this.objectPostProcessor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
|
||||
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;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Configurer for the OAuth 2.0 Authorization Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.2
|
||||
* @see OAuth2AuthorizationServerConfigurer#authorizationEndpoint
|
||||
* @see OAuth2AuthorizationEndpointFilter
|
||||
*/
|
||||
public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2Configurer {
|
||||
private RequestMatcher requestMatcher;
|
||||
private AuthenticationConverter authorizationRequestConverter;
|
||||
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
|
||||
private AuthenticationSuccessHandler authorizationResponseHandler;
|
||||
private AuthenticationFailureHandler errorResponseHandler;
|
||||
private String consentPage;
|
||||
|
||||
/**
|
||||
* Restrict for internal use only.
|
||||
*/
|
||||
OAuth2AuthorizationEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
|
||||
super(objectPostProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
|
||||
* to an instance of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} used for authenticating the request.
|
||||
*
|
||||
* @param authorizationRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
|
||||
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationEndpointConfigurer authorizationRequestConverter(AuthenticationConverter authorizationRequestConverter) {
|
||||
this.authorizationRequestConverter = authorizationRequestConverter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an {@link AuthenticationProvider} used for authenticating an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
|
||||
*
|
||||
* @param authenticationProvider an {@link AuthenticationProvider} used for authenticating an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
|
||||
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
|
||||
this.authenticationProviders.add(authenticationProvider);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
|
||||
* and returning the {@link OAuth2AuthorizationResponse Authorization Response}.
|
||||
*
|
||||
* @param authorizationResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
|
||||
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationEndpointConfigurer authorizationResponseHandler(AuthenticationSuccessHandler authorizationResponseHandler) {
|
||||
this.authorizationResponseHandler = authorizationResponseHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationException}
|
||||
* and returning the {@link OAuth2Error Error Response}.
|
||||
*
|
||||
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationException}
|
||||
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
|
||||
this.errorResponseHandler = errorResponseHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the URI to redirect Resource Owners to if consent is required during
|
||||
* the {@code authorization_code} flow. A default consent page will be generated when
|
||||
* this attribute is not specified.
|
||||
*
|
||||
* If a URI is specified, applications are required to process the specified URI to generate
|
||||
* a consent page. The query string will contain the following parameters:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@code client_id} - the client identifier</li>
|
||||
* <li>{@code scope} - a space-delimited list of scopes present in the authorization request</li>
|
||||
* <li>{@code state} - a CSRF protection token</li>
|
||||
* </ul>
|
||||
*
|
||||
* In general, the consent page should create a form that submits
|
||||
* a request with the following requirements:
|
||||
*
|
||||
* <ul>
|
||||
* <li>It must be an HTTP POST</li>
|
||||
* <li>It must be submitted to {@link ProviderSettings#getAuthorizationEndpoint()} ()}</li>
|
||||
* <li>It must include the received {@code client_id} as an HTTP parameter</li>
|
||||
* <li>It must include the received {@code state} as an HTTP parameter</li>
|
||||
* <li>It must include the list of {@code scope}s the {@code Resource Owner}
|
||||
* consented to as an HTTP parameter</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param consentPage the URI of the custom consent page to redirect to if consent is required (e.g. "/oauth2/consent")
|
||||
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationEndpointConfigurer consentPage(String consentPage) {
|
||||
this.consentPage = consentPage;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
<B extends HttpSecurityBuilder<B>> void init(B builder) {
|
||||
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(
|
||||
providerSettings.getAuthorizationEndpoint(),
|
||||
HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher(
|
||||
providerSettings.getAuthorizationEndpoint(),
|
||||
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);
|
||||
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
|
||||
|
||||
OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
|
||||
new OAuth2AuthorizationEndpointFilter(
|
||||
authenticationManager,
|
||||
providerSettings.getAuthorizationEndpoint());
|
||||
if (this.authorizationRequestConverter != null) {
|
||||
authorizationEndpointFilter.setAuthenticationConverter(this.authorizationRequestConverter);
|
||||
}
|
||||
if (this.authorizationResponseHandler != null) {
|
||||
authorizationEndpointFilter.setAuthenticationSuccessHandler(this.authorizationResponseHandler);
|
||||
}
|
||||
if (this.errorResponseHandler != null) {
|
||||
authorizationEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
|
||||
}
|
||||
if (StringUtils.hasText(this.consentPage)) {
|
||||
authorizationEndpointFilter.setConsentPage(this.consentPage);
|
||||
}
|
||||
builder.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
RequestMatcher getRequestMatcher() {
|
||||
return this.requestMatcher;
|
||||
}
|
||||
|
||||
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
|
||||
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationProvider authorizationCodeRequestAuthenticationProvider =
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(builder),
|
||||
OAuth2ConfigurerUtils.getAuthorizationConsentService(builder));
|
||||
authenticationProviders.add(authorizationCodeRequestAuthenticationProvider);
|
||||
|
||||
return authenticationProviders;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -15,27 +15,27 @@
|
||||
*/
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
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.crypto.keys.KeyManager;
|
||||
import org.springframework.security.oauth2.jose.jws.NimbusJwsEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider;
|
||||
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.web.JwkSetEndpointFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
|
||||
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;
|
||||
@@ -43,38 +43,44 @@ 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;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 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 OAuth2AuthorizationEndpointFilter
|
||||
* @see OAuth2TokenEndpointFilter
|
||||
* @see OAuth2ClientAuthenticationFilter
|
||||
* @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 RequestMatcher authorizationEndpointMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(
|
||||
OAuth2AuthorizationEndpointFilter.DEFAULT_AUTHORIZATION_ENDPOINT_URI,
|
||||
HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher(
|
||||
OAuth2AuthorizationEndpointFilter.DEFAULT_AUTHORIZATION_ENDPOINT_URI,
|
||||
HttpMethod.POST.name()));
|
||||
private final RequestMatcher tokenEndpointMatcher = new AntPathRequestMatcher(
|
||||
OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI, HttpMethod.POST.name());
|
||||
private final RequestMatcher jwkSetEndpointMatcher = new AntPathRequestMatcher(
|
||||
JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI, HttpMethod.GET.name());
|
||||
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.
|
||||
@@ -84,7 +90,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfigurer<B> registeredClientRepository(RegisteredClientRepository registeredClientRepository) {
|
||||
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
|
||||
this.getBuilder().setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
|
||||
getBuilder().setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -96,133 +102,187 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfigurer<B> authorizationService(OAuth2AuthorizationService authorizationService) {
|
||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||
this.getBuilder().setSharedObject(OAuth2AuthorizationService.class, authorizationService);
|
||||
getBuilder().setSharedObject(OAuth2AuthorizationService.class, authorizationService);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the key manager.
|
||||
* Sets the authorization consent service.
|
||||
*
|
||||
* @param keyManager the key manager
|
||||
* @param authorizationConsentService the authorization consent service
|
||||
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfigurer<B> keyManager(KeyManager keyManager) {
|
||||
Assert.notNull(keyManager, "keyManager cannot be null");
|
||||
this.getBuilder().setSharedObject(KeyManager.class, keyManager);
|
||||
public OAuth2AuthorizationServerConfigurer<B> authorizationConsentService(OAuth2AuthorizationConsentService authorizationConsentService) {
|
||||
Assert.notNull(authorizationConsentService, "authorizationConsentService cannot be null");
|
||||
getBuilder().setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code List} of {@link RequestMatcher}'s for the authorization server endpoints.
|
||||
* Sets the provider settings.
|
||||
*
|
||||
* @return a {@code List} of {@link RequestMatcher}'s for the authorization server endpoints
|
||||
* @param providerSettings the provider settings
|
||||
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
||||
*/
|
||||
public List<RequestMatcher> getEndpointMatchers() {
|
||||
return Arrays.asList(this.authorizationEndpointMatcher,
|
||||
this.tokenEndpointMatcher, this.jwkSetEndpointMatcher);
|
||||
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) {
|
||||
OAuth2ClientAuthenticationProvider clientAuthenticationProvider =
|
||||
new OAuth2ClientAuthenticationProvider(
|
||||
getRegisteredClientRepository(builder),
|
||||
getAuthorizationService(builder));
|
||||
builder.authenticationProvider(postProcess(clientAuthenticationProvider));
|
||||
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
|
||||
validateProviderSettings(providerSettings);
|
||||
initEndpointMatchers(providerSettings);
|
||||
|
||||
NimbusJwsEncoder jwtEncoder = new NimbusJwsEncoder(getKeyManager(builder));
|
||||
this.configurers.values().forEach(configurer -> configurer.init(builder));
|
||||
|
||||
OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider =
|
||||
new OAuth2AuthorizationCodeAuthenticationProvider(
|
||||
getRegisteredClientRepository(builder),
|
||||
getAuthorizationService(builder),
|
||||
jwtEncoder);
|
||||
builder.authenticationProvider(postProcess(authorizationCodeAuthenticationProvider));
|
||||
OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider =
|
||||
new OAuth2TokenIntrospectionAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(builder));
|
||||
builder.authenticationProvider(postProcess(tokenIntrospectionAuthenticationProvider));
|
||||
|
||||
OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider =
|
||||
new OAuth2ClientCredentialsAuthenticationProvider(
|
||||
getAuthorizationService(builder),
|
||||
jwtEncoder);
|
||||
builder.authenticationProvider(postProcess(clientCredentialsAuthenticationProvider));
|
||||
OAuth2TokenRevocationAuthenticationProvider tokenRevocationAuthenticationProvider =
|
||||
new OAuth2TokenRevocationAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(builder));
|
||||
builder.authenticationProvider(postProcess(tokenRevocationAuthenticationProvider));
|
||||
|
||||
ExceptionHandlingConfigurer<B> exceptionHandling = builder.getConfigurer(ExceptionHandlingConfigurer.class);
|
||||
if (exceptionHandling != null) {
|
||||
// Register the default AuthenticationEntryPoint for the token endpoint
|
||||
exceptionHandling.defaultAuthenticationEntryPointFor(
|
||||
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), this.tokenEndpointMatcher);
|
||||
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
|
||||
new OrRequestMatcher(
|
||||
getRequestMatcher(OAuth2TokenEndpointConfigurer.class),
|
||||
this.tokenIntrospectionEndpointMatcher,
|
||||
this.tokenRevocationEndpointMatcher)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(B builder) {
|
||||
JwkSetEndpointFilter jwkSetEndpointFilter = new JwkSetEndpointFilter(getKeyManager(builder));
|
||||
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
this.configurers.values().forEach(configurer -> configurer.configure(builder));
|
||||
|
||||
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
|
||||
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
|
||||
|
||||
OAuth2ClientAuthenticationFilter clientAuthenticationFilter = new OAuth2ClientAuthenticationFilter(
|
||||
authenticationManager, this.tokenEndpointMatcher);
|
||||
builder.addFilterAfter(postProcess(clientAuthenticationFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
|
||||
OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
|
||||
new OAuth2AuthorizationEndpointFilter(
|
||||
getRegisteredClientRepository(builder),
|
||||
getAuthorizationService(builder));
|
||||
builder.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
|
||||
OAuth2TokenEndpointFilter tokenEndpointFilter =
|
||||
new OAuth2TokenEndpointFilter(
|
||||
OAuth2TokenIntrospectionEndpointFilter tokenIntrospectionEndpointFilter =
|
||||
new OAuth2TokenIntrospectionEndpointFilter(
|
||||
authenticationManager,
|
||||
getAuthorizationService(builder));
|
||||
builder.addFilterAfter(postProcess(tokenEndpointFilter), FilterSecurityInterceptor.class);
|
||||
}
|
||||
providerSettings.getTokenIntrospectionEndpoint());
|
||||
builder.addFilterAfter(postProcess(tokenIntrospectionEndpointFilter), FilterSecurityInterceptor.class);
|
||||
|
||||
private static <B extends HttpSecurityBuilder<B>> RegisteredClientRepository getRegisteredClientRepository(B builder) {
|
||||
RegisteredClientRepository registeredClientRepository = builder.getSharedObject(RegisteredClientRepository.class);
|
||||
if (registeredClientRepository == null) {
|
||||
registeredClientRepository = getRegisteredClientRepositoryBean(builder);
|
||||
builder.setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
|
||||
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);
|
||||
}
|
||||
return registeredClientRepository;
|
||||
}
|
||||
|
||||
private static <B extends HttpSecurityBuilder<B>> RegisteredClientRepository getRegisteredClientRepositoryBean(B builder) {
|
||||
return builder.getSharedObject(ApplicationContext.class).getBean(RegisteredClientRepository.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;
|
||||
}
|
||||
|
||||
private static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizationService getAuthorizationService(B builder) {
|
||||
OAuth2AuthorizationService authorizationService = builder.getSharedObject(OAuth2AuthorizationService.class);
|
||||
if (authorizationService == null) {
|
||||
authorizationService = getAuthorizationServiceBean(builder);
|
||||
if (authorizationService == null) {
|
||||
authorizationService = new InMemoryOAuth2AuthorizationService();
|
||||
@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);
|
||||
}
|
||||
builder.setSharedObject(OAuth2AuthorizationService.class, authorizationService);
|
||||
}
|
||||
return authorizationService;
|
||||
}
|
||||
|
||||
private static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizationService getAuthorizationServiceBean(B builder) {
|
||||
Map<String, OAuth2AuthorizationService> authorizationServiceMap = BeanFactoryUtils.beansOfTypeIncludingAncestors(
|
||||
builder.getSharedObject(ApplicationContext.class), OAuth2AuthorizationService.class);
|
||||
if (authorizationServiceMap.size() > 1) {
|
||||
throw new NoUniqueBeanDefinitionException(OAuth2AuthorizationService.class, authorizationServiceMap.size(),
|
||||
"Expected single matching bean of type '" + OAuth2AuthorizationService.class.getName() + "' but found " +
|
||||
authorizationServiceMap.size() + ": " + StringUtils.collectionToCommaDelimitedString(authorizationServiceMap.keySet()));
|
||||
}
|
||||
return (!authorizationServiceMap.isEmpty() ? authorizationServiceMap.values().iterator().next() : null);
|
||||
}
|
||||
|
||||
private static <B extends HttpSecurityBuilder<B>> KeyManager getKeyManager(B builder) {
|
||||
KeyManager keyManager = builder.getSharedObject(KeyManager.class);
|
||||
if (keyManager == null) {
|
||||
keyManager = getKeyManagerBean(builder);
|
||||
builder.setSharedObject(KeyManager.class, keyManager);
|
||||
}
|
||||
return keyManager;
|
||||
}
|
||||
|
||||
private static <B extends HttpSecurityBuilder<B>> KeyManager getKeyManagerBean(B builder) {
|
||||
return builder.getSharedObject(ApplicationContext.class).getBean(KeyManager.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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.LinkedList;
|
||||
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.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
|
||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||
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.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* Configurer for the OAuth 2.0 Token Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.2
|
||||
* @see OAuth2AuthorizationServerConfigurer#tokenEndpoint
|
||||
* @see OAuth2TokenEndpointFilter
|
||||
*/
|
||||
public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configurer {
|
||||
private RequestMatcher requestMatcher;
|
||||
private AuthenticationConverter accessTokenRequestConverter;
|
||||
private final List<AuthenticationProvider> authenticationProviders = new LinkedList<>();
|
||||
private AuthenticationSuccessHandler accessTokenResponseHandler;
|
||||
private AuthenticationFailureHandler errorResponseHandler;
|
||||
|
||||
/**
|
||||
* Restrict for internal use only.
|
||||
*/
|
||||
OAuth2TokenEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
|
||||
super(objectPostProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest}
|
||||
* to an instance of {@link OAuth2AuthorizationGrantAuthenticationToken} used for authenticating the authorization grant.
|
||||
*
|
||||
* @param accessTokenRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest}
|
||||
* @return the {@link OAuth2TokenEndpointConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2TokenEndpointConfigurer accessTokenRequestConverter(AuthenticationConverter accessTokenRequestConverter) {
|
||||
this.accessTokenRequestConverter = accessTokenRequestConverter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an {@link AuthenticationProvider} used for authenticating a type of {@link OAuth2AuthorizationGrantAuthenticationToken}.
|
||||
*
|
||||
* @param authenticationProvider an {@link AuthenticationProvider} used for authenticating a type of {@link OAuth2AuthorizationGrantAuthenticationToken}
|
||||
* @return the {@link OAuth2TokenEndpointConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2TokenEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
|
||||
this.authenticationProviders.add(authenticationProvider);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AccessTokenAuthenticationToken}
|
||||
* and returning the {@link OAuth2AccessTokenResponse Access Token Response}.
|
||||
*
|
||||
* @param accessTokenResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AccessTokenAuthenticationToken}
|
||||
* @return the {@link OAuth2TokenEndpointConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2TokenEndpointConfigurer accessTokenResponseHandler(AuthenticationSuccessHandler accessTokenResponseHandler) {
|
||||
this.accessTokenResponseHandler = accessTokenResponseHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
|
||||
* and returning the {@link OAuth2Error Error Response}.
|
||||
*
|
||||
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
|
||||
* @return the {@link OAuth2TokenEndpointConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2TokenEndpointConfigurer 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 AntPathRequestMatcher(
|
||||
providerSettings.getTokenEndpoint(), 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);
|
||||
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
|
||||
|
||||
OAuth2TokenEndpointFilter tokenEndpointFilter =
|
||||
new OAuth2TokenEndpointFilter(
|
||||
authenticationManager,
|
||||
providerSettings.getTokenEndpoint());
|
||||
if (this.accessTokenRequestConverter != null) {
|
||||
tokenEndpointFilter.setAuthenticationConverter(this.accessTokenRequestConverter);
|
||||
}
|
||||
if (this.accessTokenResponseHandler != null) {
|
||||
tokenEndpointFilter.setAuthenticationSuccessHandler(this.accessTokenResponseHandler);
|
||||
}
|
||||
if (this.errorResponseHandler != null) {
|
||||
tokenEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
|
||||
}
|
||||
builder.addFilterAfter(postProcess(tokenEndpointFilter), FilterSecurityInterceptor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
RequestMatcher getRequestMatcher() {
|
||||
return this.requestMatcher;
|
||||
}
|
||||
|
||||
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
|
||||
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
|
||||
|
||||
JwtEncoder jwtEncoder = OAuth2ConfigurerUtils.getJwtEncoder(builder);
|
||||
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = OAuth2ConfigurerUtils.getJwtCustomizer(builder);
|
||||
|
||||
OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider =
|
||||
new OAuth2AuthorizationCodeAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(builder),
|
||||
jwtEncoder);
|
||||
if (jwtCustomizer != null) {
|
||||
authorizationCodeAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
|
||||
}
|
||||
authenticationProviders.add(authorizationCodeAuthenticationProvider);
|
||||
|
||||
OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider =
|
||||
new OAuth2RefreshTokenAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(builder),
|
||||
jwtEncoder);
|
||||
if (jwtCustomizer != null) {
|
||||
refreshTokenAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
|
||||
}
|
||||
authenticationProviders.add(refreshTokenAuthenticationProvider);
|
||||
|
||||
OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider =
|
||||
new OAuth2ClientCredentialsAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(builder),
|
||||
jwtEncoder);
|
||||
if (jwtCustomizer != null) {
|
||||
clientCredentialsAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
|
||||
}
|
||||
authenticationProviders.add(clientCredentialsAuthenticationProvider);
|
||||
|
||||
return authenticationProviders;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.crypto.keys;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are responsible for the management of {@link ManagedKey}(s),
|
||||
* e.g. {@code javax.crypto.SecretKey}, {@code java.security.PrivateKey}, {@code java.security.PublicKey}, etc.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
* @see ManagedKey
|
||||
*/
|
||||
public interface KeyManager {
|
||||
|
||||
/**
|
||||
* Returns the {@link ManagedKey} identified by the provided {@code keyId},
|
||||
* or {@code null} if not found.
|
||||
*
|
||||
* @param keyId the key ID
|
||||
* @return the {@link ManagedKey}, or {@code null} if not found
|
||||
*/
|
||||
@Nullable
|
||||
ManagedKey findByKeyId(String keyId);
|
||||
|
||||
/**
|
||||
* Returns a {@code Set} of {@link ManagedKey}(s) having the provided key {@code algorithm},
|
||||
* or an empty {@code Set} if not found.
|
||||
*
|
||||
* @param algorithm the key algorithm
|
||||
* @return a {@code Set} of {@link ManagedKey}(s), or an empty {@code Set} if not found
|
||||
*/
|
||||
Set<ManagedKey> findByAlgorithm(String algorithm);
|
||||
|
||||
/**
|
||||
* Returns a {@code Set} of the {@link ManagedKey}(s).
|
||||
*
|
||||
* @return a {@code Set} of the {@link ManagedKey}(s)
|
||||
*/
|
||||
Set<ManagedKey> getKeys();
|
||||
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.crypto.keys;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.oauth2.server.authorization.Version;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.io.Serializable;
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A {@code java.security.Key} that is managed by a {@link KeyManager}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
* @see KeyManager
|
||||
*/
|
||||
public final class ManagedKey implements Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
private Key key;
|
||||
private PublicKey publicKey;
|
||||
private String keyId;
|
||||
private Instant activatedOn;
|
||||
private Instant deactivatedOn;
|
||||
|
||||
private ManagedKey() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this is a symmetric key, {@code false} otherwise.
|
||||
*
|
||||
* @return {@code true} if this is a symmetric key, {@code false} otherwise
|
||||
*/
|
||||
public boolean isSymmetric() {
|
||||
return SecretKey.class.isAssignableFrom(this.key.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this is a asymmetric key, {@code false} otherwise.
|
||||
*
|
||||
* @return {@code true} if this is a asymmetric key, {@code false} otherwise
|
||||
*/
|
||||
public boolean isAsymmetric() {
|
||||
return PrivateKey.class.isAssignableFrom(this.key.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a type of {@code java.security.Key},
|
||||
* e.g. {@code javax.crypto.SecretKey} or {@code java.security.PrivateKey}.
|
||||
*
|
||||
* @param <T> the type of {@code java.security.Key}
|
||||
* @return the type of {@code java.security.Key}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Key> T getKey() {
|
||||
return (T) this.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code java.security.PublicKey} if this is a asymmetric key, {@code null} otherwise.
|
||||
*
|
||||
* @return the {@code java.security.PublicKey} if this is a asymmetric key, {@code null} otherwise
|
||||
*/
|
||||
@Nullable
|
||||
public PublicKey getPublicKey() {
|
||||
return this.publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key ID.
|
||||
*
|
||||
* @return the key ID
|
||||
*/
|
||||
public String getKeyId() {
|
||||
return this.keyId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time when this key was activated.
|
||||
*
|
||||
* @return the time when this key was activated
|
||||
*/
|
||||
public Instant getActivatedOn() {
|
||||
return this.activatedOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time when this key was deactivated, {@code null} if still active.
|
||||
*
|
||||
* @return the time when this key was deactivated, {@code null} if still active
|
||||
*/
|
||||
@Nullable
|
||||
public Instant getDeactivatedOn() {
|
||||
return this.deactivatedOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this key is active, {@code false} otherwise.
|
||||
*
|
||||
* @return {@code true} if this key is active, {@code false} otherwise
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return getDeactivatedOn() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key algorithm.
|
||||
*
|
||||
* @return the key algorithm
|
||||
*/
|
||||
public String getAlgorithm() {
|
||||
return this.key.getAlgorithm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ManagedKey that = (ManagedKey) obj;
|
||||
return Objects.equals(this.keyId, that.keyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.keyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link Builder}, initialized with the provided {@code javax.crypto.SecretKey}.
|
||||
*
|
||||
* @param secretKey the {@code javax.crypto.SecretKey}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder withSymmetricKey(SecretKey secretKey) {
|
||||
return new Builder(secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link Builder}, initialized with the provided
|
||||
* {@code java.security.PublicKey} and {@code java.security.PrivateKey}.
|
||||
*
|
||||
* @param publicKey the {@code java.security.PublicKey}
|
||||
* @param privateKey the {@code java.security.PrivateKey}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder withAsymmetricKey(PublicKey publicKey, PrivateKey privateKey) {
|
||||
return new Builder(publicKey, privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link ManagedKey}.
|
||||
*/
|
||||
public static class Builder {
|
||||
private Key key;
|
||||
private PublicKey publicKey;
|
||||
private String keyId;
|
||||
private Instant activatedOn;
|
||||
private Instant deactivatedOn;
|
||||
|
||||
private Builder(SecretKey secretKey) {
|
||||
Assert.notNull(secretKey, "secretKey cannot be null");
|
||||
this.key = secretKey;
|
||||
}
|
||||
|
||||
private Builder(PublicKey publicKey, PrivateKey privateKey) {
|
||||
Assert.notNull(publicKey, "publicKey cannot be null");
|
||||
Assert.notNull(privateKey, "privateKey cannot be null");
|
||||
this.key = privateKey;
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the key ID.
|
||||
*
|
||||
* @param keyId the key ID
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder keyId(String keyId) {
|
||||
this.keyId = keyId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time when this key was activated.
|
||||
*
|
||||
* @param activatedOn the time when this key was activated
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder activatedOn(Instant activatedOn) {
|
||||
this.activatedOn = activatedOn;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time when this key was deactivated.
|
||||
*
|
||||
* @param deactivatedOn the time when this key was deactivated
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder deactivatedOn(Instant deactivatedOn) {
|
||||
this.deactivatedOn = deactivatedOn;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link ManagedKey}.
|
||||
*
|
||||
* @return a {@link ManagedKey}
|
||||
*/
|
||||
public ManagedKey build() {
|
||||
Assert.hasText(this.keyId, "keyId cannot be empty");
|
||||
Assert.notNull(this.activatedOn, "activatedOn cannot be null");
|
||||
|
||||
ManagedKey managedKey = new ManagedKey();
|
||||
managedKey.key = this.key;
|
||||
managedKey.publicKey = this.publicKey;
|
||||
managedKey.keyId = this.keyId;
|
||||
managedKey.activatedOn = this.activatedOn;
|
||||
managedKey.deactivatedOn = this.deactivatedOn;
|
||||
return managedKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.crypto.keys;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.KeyPair;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateRsaKey;
|
||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateSecretKey;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link KeyManager} that generates the {@link ManagedKey}(s) when constructed.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This implementation should ONLY be used during development/testing.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
* @see KeyManager
|
||||
*/
|
||||
public final class StaticKeyGeneratingKeyManager implements KeyManager {
|
||||
private final Map<String, ManagedKey> keys;
|
||||
|
||||
public StaticKeyGeneratingKeyManager() {
|
||||
this.keys = Collections.unmodifiableMap(new HashMap<>(generateKeys()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ManagedKey findByKeyId(String keyId) {
|
||||
Assert.hasText(keyId, "keyId cannot be empty");
|
||||
return this.keys.get(keyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ManagedKey> findByAlgorithm(String algorithm) {
|
||||
Assert.hasText(algorithm, "algorithm cannot be empty");
|
||||
return this.keys.values().stream()
|
||||
.filter(managedKey -> managedKey.getAlgorithm().equals(algorithm))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ManagedKey> getKeys() {
|
||||
return new HashSet<>(this.keys.values());
|
||||
}
|
||||
|
||||
private static Map<String, ManagedKey> generateKeys() {
|
||||
KeyPair rsaKeyPair = generateRsaKey();
|
||||
ManagedKey rsaManagedKey = ManagedKey.withAsymmetricKey(rsaKeyPair.getPublic(), rsaKeyPair.getPrivate())
|
||||
.keyId(UUID.randomUUID().toString())
|
||||
.activatedOn(Instant.now())
|
||||
.build();
|
||||
|
||||
SecretKey hmacKey = generateSecretKey();
|
||||
ManagedKey secretManagedKey = ManagedKey.withSymmetricKey(hmacKey)
|
||||
.keyId(UUID.randomUUID().toString())
|
||||
.activatedOn(Instant.now())
|
||||
.build();
|
||||
|
||||
return Stream.of(rsaManagedKey, secretManagedKey)
|
||||
.collect(Collectors.toMap(ManagedKey::getKeyId, v -> v));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A base representation of OAuth 2.0 Authorization Server metadata,
|
||||
* returned by an endpoint defined in OAuth 2.0 Authorization Server Metadata and OpenID Connect Discovery 1.0.
|
||||
* The metadata endpoint returns a set of claims an Authorization Server describes about its configuration.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @see OAuth2AuthorizationServerMetadataClaimAccessor
|
||||
* @since 0.1.1
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-3.2">3.2. Authorization Server Metadata Response</a>
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">4.2. OpenID Provider Configuration Response</a>
|
||||
*/
|
||||
public abstract class AbstractOAuth2AuthorizationServerMetadata implements OAuth2AuthorizationServerMetadataClaimAccessor, Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
private final Map<String, Object> claims;
|
||||
|
||||
protected AbstractOAuth2AuthorizationServerMetadata(Map<String, Object> claims) {
|
||||
Assert.notEmpty(claims, "claims cannot be empty");
|
||||
this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the metadata as claims.
|
||||
*
|
||||
* @return a {@code Map} of the metadata as claims
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getClaims() {
|
||||
return this.claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for subclasses of {@link AbstractOAuth2AuthorizationServerMetadata}.
|
||||
*/
|
||||
protected static abstract class AbstractBuilder<T extends AbstractOAuth2AuthorizationServerMetadata, B extends AbstractBuilder<T, B>> {
|
||||
private final Map<String, Object> claims = new LinkedHashMap<>();
|
||||
|
||||
protected AbstractBuilder() {
|
||||
}
|
||||
|
||||
protected Map<String, Object> getClaims() {
|
||||
return this.claims;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final B getThis() {
|
||||
return (B) this; // avoid unchecked casts in subclasses by using "getThis()" instead of "(B) this"
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code issuer} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED.
|
||||
*
|
||||
* @param issuer the {@code URL} of the Authorization Server's Issuer Identifier
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B issuer(String issuer) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, issuer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code authorization_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED.
|
||||
*
|
||||
* @param authorizationEndpoint the {@code URL} of the OAuth 2.0 Authorization Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B authorizationEndpoint(String authorizationEndpoint) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, authorizationEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code token_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED.
|
||||
*
|
||||
* @param tokenEndpoint the {@code URL} of the OAuth 2.0 Token Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenEndpoint(String tokenEndpoint) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, tokenEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this client authentication method to the collection of {@code token_endpoint_auth_methods_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param authenticationMethod the client authentication method supported by the OAuth 2.0 Token Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenEndpointAuthenticationMethod(String authenticationMethod) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethod);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the client authentication method(s) allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param authenticationMethodsConsumer a {@code Consumer} of the client authentication method(s) supported by the OAuth 2.0 Token Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenEndpointAuthenticationMethods(Consumer<List<String>> authenticationMethodsConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethodsConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code jwks_uri} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param jwkSetUrl the {@code URL} of the JSON Web Key Set
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B jwkSetUrl(String jwkSetUrl) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, jwkSetUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this OAuth 2.0 {@code scope} to the collection of {@code scopes_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, RECOMMENDED.
|
||||
*
|
||||
* @param scope the OAuth 2.0 {@code scope} value supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B scope(String scope) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, scope);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the OAuth 2.0 {@code scope} values supported allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param scopesConsumer a {@code Consumer} of the OAuth 2.0 {@code scope} values supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B scopes(Consumer<List<String>> scopesConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, scopesConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this OAuth 2.0 {@code response_type} to the collection of {@code response_types_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED.
|
||||
*
|
||||
* @param responseType the OAuth 2.0 {@code response_type} value supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B responseType(String responseType) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, responseType);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the OAuth 2.0 {@code response_type} values supported allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param responseTypesConsumer a {@code Consumer} of the OAuth 2.0 {@code response_type} values supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B responseTypes(Consumer<List<String>> responseTypesConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, responseTypesConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this OAuth 2.0 {@code grant_type} to the collection of {@code grant_types_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param grantType the OAuth 2.0 {@code grant_type} value supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B grantType(String grantType) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, grantType);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the OAuth 2.0 {@code grant_type} values supported allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param grantTypesConsumer a {@code Consumer} of the OAuth 2.0 {@code grant_type} values supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B grantTypes(Consumer<List<String>> grantTypesConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, grantTypesConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code revocation_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param tokenRevocationEndpoint the {@code URL} of the OAuth 2.0 Token Revocation Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenRevocationEndpoint(String tokenRevocationEndpoint) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, tokenRevocationEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this client authentication method to the collection of {@code revocation_endpoint_auth_methods_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param authenticationMethod the client authentication method supported by the OAuth 2.0 Token Revocation Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenRevocationEndpointAuthenticationMethod(String authenticationMethod) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethod);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the client authentication method(s) allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param authenticationMethodsConsumer a {@code Consumer} of the client authentication method(s) supported by the OAuth 2.0 Token Revocation Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenRevocationEndpointAuthenticationMethods(Consumer<List<String>> authenticationMethodsConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethodsConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code introspection_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param tokenIntrospectionEndpoint the {@code URL} of the OAuth 2.0 Token Introspection Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenIntrospectionEndpoint(String tokenIntrospectionEndpoint) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT, tokenIntrospectionEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this client authentication method to the collection of {@code introspection_endpoint_auth_methods_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param authenticationMethod the client authentication method supported by the OAuth 2.0 Token Introspection Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenIntrospectionEndpointAuthenticationMethod(String authenticationMethod) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethod);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the client authentication method(s) allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param authenticationMethodsConsumer a {@code Consumer} of the client authentication method(s) supported by the OAuth 2.0 Token Introspection Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenIntrospectionEndpointAuthenticationMethods(Consumer<List<String>> authenticationMethodsConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethodsConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this Proof Key for Code Exchange (PKCE) {@code code_challenge_method} to the collection of {@code code_challenge_methods_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param codeChallengeMethod the {@code code_challenge_method} value supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B codeChallengeMethod(String codeChallengeMethod) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, codeChallengeMethod);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the Proof Key for Code Exchange (PKCE) {@code code_challenge_method} values supported allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param codeChallengeMethodsConsumer a {@code Consumer} of the {@code code_challenge_method} values supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B codeChallengeMethods(Consumer<List<String>> codeChallengeMethodsConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, codeChallengeMethodsConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this claim in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}.
|
||||
*
|
||||
* @param name the claim name
|
||||
* @param value the claim value
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B claim(String name, Object value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
this.claims.put(name, value);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to every {@link #claim(String, Object)} declared so far with
|
||||
* the possibility to add, replace, or remove.
|
||||
*
|
||||
* @param claimsConsumer a {@code Consumer} of the claims
|
||||
* @return the {@link AbstractBuilder} for further configurations
|
||||
*/
|
||||
public B claims(Consumer<Map<String, Object>> claimsConsumer) {
|
||||
claimsConsumer.accept(this.claims);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link AbstractOAuth2AuthorizationServerMetadata}.
|
||||
*
|
||||
* @return the {@link AbstractOAuth2AuthorizationServerMetadata}
|
||||
*/
|
||||
public abstract T build();
|
||||
|
||||
protected void validate() {
|
||||
Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.ISSUER), "issuer cannot be null");
|
||||
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.ISSUER), "issuer must be a valid URL");
|
||||
Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT), "authorizationEndpoint cannot be null");
|
||||
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT), "authorizationEndpoint must be a valid URL");
|
||||
Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT), "tokenEndpoint cannot be null");
|
||||
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT), "tokenEndpoint must be a valid URL");
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED) != null) {
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenEndpointAuthenticationMethods must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenEndpointAuthenticationMethods cannot be empty");
|
||||
}
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI) != null) {
|
||||
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI), "jwksUri must be a valid URL");
|
||||
}
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED) != null) {
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED), "scopes must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED), "scopes cannot be empty");
|
||||
}
|
||||
Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes cannot be null");
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes cannot be empty");
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED) != null) {
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED), "grantTypes must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED), "grantTypes cannot be empty");
|
||||
}
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT) != null) {
|
||||
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT), "tokenRevocationEndpoint must be a valid URL");
|
||||
}
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED) != null) {
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenRevocationEndpointAuthenticationMethods must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenRevocationEndpointAuthenticationMethods cannot be empty");
|
||||
}
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT) != null) {
|
||||
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT), "tokenIntrospectionEndpoint must be a valid URL");
|
||||
}
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED) != null) {
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenIntrospectionEndpointAuthenticationMethods must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenIntrospectionEndpointAuthenticationMethods cannot be empty");
|
||||
}
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED) != null) {
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED), "codeChallengeMethods must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED), "codeChallengeMethods cannot be empty");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void addClaimToClaimList(String name, String value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
getClaims().computeIfAbsent(name, k -> new LinkedList<String>());
|
||||
((List<String>) getClaims().get(name)).add(value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void acceptClaimValues(String name, Consumer<List<String>> valuesConsumer) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(valuesConsumer, "valuesConsumer cannot be null");
|
||||
getClaims().computeIfAbsent(name, k -> new LinkedList<String>());
|
||||
List<String> values = (List<String>) getClaims().get(name);
|
||||
valuesConsumer.accept(values);
|
||||
}
|
||||
|
||||
private static void validateURL(Object url, String errorMessage) {
|
||||
if (URL.class.isAssignableFrom(url.getClass())) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
new URI(url.toString()).toURL();
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException(errorMessage, ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AbstractOAuth2Token}
|
||||
* representing an OAuth 2.0 Authorization Code Grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.3
|
||||
* @see AbstractOAuth2Token
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant</a>
|
||||
*/
|
||||
public class OAuth2AuthorizationCode extends AbstractOAuth2Token {
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizationCode} using the provided parameters.
|
||||
* @param tokenValue the token value
|
||||
* @param issuedAt the time at which the token was issued
|
||||
* @param expiresAt the time at which the token expires
|
||||
*/
|
||||
public OAuth2AuthorizationCode(String tokenValue, Instant issuedAt, Instant expiresAt) {
|
||||
super(tokenValue, issuedAt, expiresAt);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A representation of an OAuth 2.0 Authorization Server Metadata response,
|
||||
* which is returned from an OAuth 2.0 Authorization Server's Metadata Endpoint,
|
||||
* and contains a set of claims about the Authorization Server's configuration.
|
||||
* The claims are defined by the OAuth 2.0 Authorization Server Metadata
|
||||
* specification (RFC 8414).
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.1
|
||||
* @see AbstractOAuth2AuthorizationServerMetadata
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-3.2">3.2. Authorization Server Metadata Response</a>
|
||||
*/
|
||||
public final class OAuth2AuthorizationServerMetadata extends AbstractOAuth2AuthorizationServerMetadata {
|
||||
|
||||
private OAuth2AuthorizationServerMetadata(Map<String, Object> claims) {
|
||||
super(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} with empty claims.
|
||||
*
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} with the provided claims.
|
||||
*
|
||||
* @param claims the claims to initialize the builder
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder withClaims(Map<String, Object> claims) {
|
||||
Assert.notEmpty(claims, "claims cannot be empty");
|
||||
return new Builder()
|
||||
.claims(c -> c.putAll(claims));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helps configure an {@link OAuth2AuthorizationServerMetadata}.
|
||||
*/
|
||||
public static class Builder extends AbstractBuilder<OAuth2AuthorizationServerMetadata, Builder> {
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the claims and build the {@link OAuth2AuthorizationServerMetadata}.
|
||||
* <p>
|
||||
* The following claims are REQUIRED:
|
||||
* {@code issuer}, {@code authorization_endpoint}, {@code token_endpoint}
|
||||
* and {@code response_types_supported}.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizationServerMetadata}
|
||||
*/
|
||||
@Override
|
||||
public OAuth2AuthorizationServerMetadata build() {
|
||||
validate();
|
||||
return new OAuth2AuthorizationServerMetadata(getClaims());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link ClaimAccessor} for the "claims" an Authorization Server describes about its configuration,
|
||||
* used in OAuth 2.0 Authorization Server Metadata and OpenID Connect Discovery 1.0.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.1
|
||||
* @see ClaimAccessor
|
||||
* @see OAuth2AuthorizationServerMetadataClaimNames
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-2">2. Authorization Server Metadata</a>
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
|
||||
*/
|
||||
public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAccessor {
|
||||
|
||||
/**
|
||||
* Returns the {@code URL} the Authorization Server asserts as its Issuer Identifier {@code (issuer)}.
|
||||
*
|
||||
* @return the {@code URL} the Authorization Server asserts as its Issuer Identifier
|
||||
*/
|
||||
default URL getIssuer() {
|
||||
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.ISSUER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code URL} of the OAuth 2.0 Authorization Endpoint {@code (authorization_endpoint)}.
|
||||
*
|
||||
* @return the {@code URL} of the OAuth 2.0 Authorization Endpoint
|
||||
*/
|
||||
default URL getAuthorizationEndpoint() {
|
||||
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code URL} of the OAuth 2.0 Token Endpoint {@code (token_endpoint)}.
|
||||
*
|
||||
* @return the {@code URL} of the OAuth 2.0 Token Endpoint
|
||||
*/
|
||||
default URL getTokenEndpoint() {
|
||||
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client authentication methods supported by the OAuth 2.0 Token Endpoint {@code (token_endpoint_auth_methods_supported)}.
|
||||
*
|
||||
* @return the client authentication methods supported by the OAuth 2.0 Token Endpoint
|
||||
*/
|
||||
default List<String> getTokenEndpointAuthenticationMethods() {
|
||||
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code URL} of the JSON Web Key Set {@code (jwks_uri)}.
|
||||
*
|
||||
* @return the {@code URL} of the JSON Web Key Set
|
||||
*/
|
||||
default URL getJwkSetUrl() {
|
||||
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OAuth 2.0 {@code scope} values supported {@code (scopes_supported)}.
|
||||
*
|
||||
* @return the OAuth 2.0 {@code scope} values supported
|
||||
*/
|
||||
default List<String> getScopes() {
|
||||
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OAuth 2.0 {@code response_type} values supported {@code (response_types_supported)}.
|
||||
*
|
||||
* @return the OAuth 2.0 {@code response_type} values supported
|
||||
*/
|
||||
default List<String> getResponseTypes() {
|
||||
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OAuth 2.0 {@code grant_type} values supported {@code (grant_types_supported)}.
|
||||
*
|
||||
* @return the OAuth 2.0 {@code grant_type} values supported
|
||||
*/
|
||||
default List<String> getGrantTypes() {
|
||||
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code URL} of the OAuth 2.0 Token Revocation Endpoint {@code (revocation_endpoint)}.
|
||||
*
|
||||
* @return the {@code URL} of the OAuth 2.0 Token Revocation Endpoint
|
||||
*/
|
||||
default URL getTokenRevocationEndpoint() {
|
||||
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client authentication methods supported by the OAuth 2.0 Token Revocation Endpoint {@code (revocation_endpoint_auth_methods_supported)}.
|
||||
*
|
||||
* @return the client authentication methods supported by the OAuth 2.0 Token Revocation Endpoint
|
||||
*/
|
||||
default List<String> getTokenRevocationEndpointAuthenticationMethods() {
|
||||
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code URL} of the OAuth 2.0 Token Introspection Endpoint {@code (introspection_endpoint)}.
|
||||
*
|
||||
* @return the {@code URL} of the OAuth 2.0 Token Introspection Endpoint
|
||||
*/
|
||||
default URL getTokenIntrospectionEndpoint() {
|
||||
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client authentication methods supported by the OAuth 2.0 Token Introspection Endpoint {@code (introspection_endpoint_auth_methods_supported)}.
|
||||
*
|
||||
* @return the client authentication methods supported by the OAuth 2.0 Token Introspection Endpoint
|
||||
*/
|
||||
default List<String> getTokenIntrospectionEndpointAuthenticationMethods() {
|
||||
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Proof Key for Code Exchange (PKCE) {@code code_challenge_method} values supported {@code (code_challenge_methods_supported)}.
|
||||
*
|
||||
* @return the {@code code_challenge_method} values supported
|
||||
*/
|
||||
default List<String> getCodeChallengeMethods() {
|
||||
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The names of the "claims" an Authorization Server describes about its configuration,
|
||||
* used in OAuth 2.0 Authorization Server Metadata and OpenID Connect Discovery 1.0.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.1
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-2">2. Authorization Server Metadata</a>
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
|
||||
*/
|
||||
public interface OAuth2AuthorizationServerMetadataClaimNames {
|
||||
|
||||
/**
|
||||
* {@code issuer} - the {@code URL} the Authorization Server asserts as its Issuer Identifier
|
||||
*/
|
||||
String ISSUER = "issuer";
|
||||
|
||||
/**
|
||||
* {@code authorization_endpoint} - the {@code URL} of the OAuth 2.0 Authorization Endpoint
|
||||
*/
|
||||
String AUTHORIZATION_ENDPOINT = "authorization_endpoint";
|
||||
|
||||
/**
|
||||
* {@code token_endpoint} - the {@code URL} of the OAuth 2.0 Token Endpoint
|
||||
*/
|
||||
String TOKEN_ENDPOINT = "token_endpoint";
|
||||
|
||||
/**
|
||||
* {@code token_endpoint_auth_methods_supported} - the client authentication methods supported by the OAuth 2.0 Token Endpoint
|
||||
*/
|
||||
String TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED = "token_endpoint_auth_methods_supported";
|
||||
|
||||
/**
|
||||
* {@code jwks_uri} - the {@code URL} of the JSON Web Key Set
|
||||
*/
|
||||
String JWKS_URI = "jwks_uri";
|
||||
|
||||
/**
|
||||
* {@code scopes_supported} - the OAuth 2.0 {@code scope} values supported
|
||||
*/
|
||||
String SCOPES_SUPPORTED = "scopes_supported";
|
||||
|
||||
/**
|
||||
* {@code response_types_supported} - the OAuth 2.0 {@code response_type} values supported
|
||||
*/
|
||||
String RESPONSE_TYPES_SUPPORTED = "response_types_supported";
|
||||
|
||||
/**
|
||||
* {@code grant_types_supported} - the OAuth 2.0 {@code grant_type} values supported
|
||||
*/
|
||||
String GRANT_TYPES_SUPPORTED = "grant_types_supported";
|
||||
|
||||
/**
|
||||
* {@code revocation_endpoint} - the {@code URL} of the OAuth 2.0 Token Revocation Endpoint
|
||||
*/
|
||||
String REVOCATION_ENDPOINT = "revocation_endpoint";
|
||||
|
||||
/**
|
||||
* {@code revocation_endpoint_auth_methods_supported} - the client authentication methods supported by the OAuth 2.0 Token Revocation Endpoint
|
||||
*/
|
||||
String REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED = "revocation_endpoint_auth_methods_supported";
|
||||
|
||||
/**
|
||||
* {@code introspection_endpoint} - the {@code URL} of the OAuth 2.0 Token Introspection Endpoint
|
||||
*/
|
||||
String INTROSPECTION_ENDPOINT = "introspection_endpoint";
|
||||
|
||||
/**
|
||||
* {@code introspection_endpoint_auth_methods_supported} - the client authentication methods supported by the OAuth 2.0 Token Introspection Endpoint
|
||||
*/
|
||||
String INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED = "introspection_endpoint_auth_methods_supported";
|
||||
|
||||
/**
|
||||
* {@code code_challenge_methods_supported} - the Proof Key for Code Exchange (PKCE) {@code code_challenge_method} values supported
|
||||
*/
|
||||
String CODE_CHALLENGE_METHODS_SUPPORTED = "code_challenge_methods_supported";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A representation of the claims returned in an OAuth 2.0 Token Introspection Response.
|
||||
*
|
||||
* @author Gerardo Roza
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.1
|
||||
* @see OAuth2TokenIntrospectionClaimAccessor
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7662#section-2.2">Section 2.2 Introspection Response</a>
|
||||
*/
|
||||
public final class OAuth2TokenIntrospection implements OAuth2TokenIntrospectionClaimAccessor, Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
private final Map<String, Object> claims;
|
||||
|
||||
private OAuth2TokenIntrospection(Map<String, Object> claims) {
|
||||
this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the claims in the Token Introspection Response.
|
||||
*
|
||||
* @return a {@code Map} of the claims
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getClaims() {
|
||||
return this.claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} initialized with the {@link #isActive() active} claim to {@code false}.
|
||||
*
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return builder(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} initialized with the provided {@link #isActive() active} claim.
|
||||
*
|
||||
* @param active {@code true} if the token is currently active, {@code false} otherwise
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder builder(boolean active) {
|
||||
return new Builder(active);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} initialized with the provided claims.
|
||||
*
|
||||
* @param claims the claims to initialize the builder
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder withClaims(Map<String, Object> claims) {
|
||||
Assert.notEmpty(claims, "claims cannot be empty");
|
||||
return builder().claims(c -> c.putAll(claims));
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link OAuth2TokenIntrospection}.
|
||||
*/
|
||||
public static class Builder {
|
||||
private final Map<String, Object> claims = new LinkedHashMap<>();
|
||||
|
||||
private Builder(boolean active) {
|
||||
active(active);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the indicator of whether or not the presented token is currently active, REQUIRED.
|
||||
*
|
||||
* @param active {@code true} if the token is currently active, {@code false} otherwise
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder active(boolean active) {
|
||||
return claim(OAuth2TokenIntrospectionClaimNames.ACTIVE, active);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the scope associated with this token, OPTIONAL.
|
||||
*
|
||||
* @param scope the scope associated with this token
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder scope(String scope) {
|
||||
addClaimToClaimList(OAuth2TokenIntrospectionClaimNames.SCOPE, scope);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the scope(s) associated with this token,
|
||||
* allowing the ability to add, replace, or remove, OPTIONAL.
|
||||
*
|
||||
* @param scopesConsumer a {@code Consumer} of the scope(s) associated with this token
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder scopes(Consumer<List<String>> scopesConsumer) {
|
||||
acceptClaimValues(OAuth2TokenIntrospectionClaimNames.SCOPE, scopesConsumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client identifier for the OAuth 2.0 client that requested this token, OPTIONAL.
|
||||
*
|
||||
* @param clientId the client identifier for the OAuth 2.0 client that requested this token
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder clientId(String clientId) {
|
||||
return claim(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the human-readable identifier for the resource owner who authorized this token, OPTIONAL.
|
||||
*
|
||||
* @param username the human-readable identifier for the resource owner who authorized this token
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder username(String username) {
|
||||
return claim(OAuth2TokenIntrospectionClaimNames.USERNAME, username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the token type (e.g. bearer), OPTIONAL.
|
||||
*
|
||||
* @param tokenType the token type
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder tokenType(String tokenType) {
|
||||
return claim(OAuth2TokenIntrospectionClaimNames.TOKEN_TYPE, tokenType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time indicating when this token will expire, OPTIONAL.
|
||||
*
|
||||
* @param expiresAt the time indicating when this token will expire
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder expiresAt(Instant expiresAt) {
|
||||
return claim(OAuth2TokenIntrospectionClaimNames.EXP, expiresAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time indicating when this token was originally issued, OPTIONAL.
|
||||
*
|
||||
* @param issuedAt the time indicating when this token was originally issued
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder issuedAt(Instant issuedAt) {
|
||||
return claim(OAuth2TokenIntrospectionClaimNames.IAT, issuedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time indicating when this token is not to be used before, OPTIONAL.
|
||||
*
|
||||
* @param notBefore the time indicating when this token is not to be used before
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder notBefore(Instant notBefore) {
|
||||
return claim(OAuth2TokenIntrospectionClaimNames.NBF, notBefore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subject of the token, usually a machine-readable identifier
|
||||
* of the resource owner who authorized this token, OPTIONAL.
|
||||
*
|
||||
* @param subject the subject of the token
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder subject(String subject) {
|
||||
return claim(OAuth2TokenIntrospectionClaimNames.SUB, subject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the identifier representing the intended audience for this token, OPTIONAL.
|
||||
*
|
||||
* @param audience the identifier representing the intended audience for this token
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder audience(String audience) {
|
||||
addClaimToClaimList(OAuth2TokenIntrospectionClaimNames.AUD, audience);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the intended audience(s) for this token,
|
||||
* allowing the ability to add, replace, or remove, OPTIONAL.
|
||||
*
|
||||
* @param audiencesConsumer a {@code Consumer} of the intended audience(s) for this token
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder audiences(Consumer<List<String>> audiencesConsumer) {
|
||||
acceptClaimValues(OAuth2TokenIntrospectionClaimNames.AUD, audiencesConsumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the issuer of this token, OPTIONAL.
|
||||
*
|
||||
* @param issuer the issuer of this token
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder issuer(String issuer) {
|
||||
return claim(OAuth2TokenIntrospectionClaimNames.ISS, issuer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the identifier for the token, OPTIONAL.
|
||||
*
|
||||
* @param jti the identifier for the token
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder id(String jti) {
|
||||
return claim(OAuth2TokenIntrospectionClaimNames.JTI, jti);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the claim.
|
||||
*
|
||||
* @param name the claim name
|
||||
* @param value the claim value
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder claim(String name, Object value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
this.claims.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to every {@link #claim(String, Object)} declared so far with
|
||||
* the possibility to add, replace, or remove.
|
||||
*
|
||||
* @param claimsConsumer a {@code Consumer} of the claims
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
public Builder claims(Consumer<Map<String, Object>> claimsConsumer) {
|
||||
claimsConsumer.accept(this.claims);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the claims and build the {@link OAuth2TokenIntrospection}.
|
||||
* <p>
|
||||
* The following claims are REQUIRED: {@code active}
|
||||
*
|
||||
* @return the {@link OAuth2TokenIntrospection}
|
||||
*/
|
||||
public OAuth2TokenIntrospection build() {
|
||||
validate();
|
||||
return new OAuth2TokenIntrospection(this.claims);
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
Assert.notNull(this.claims.get(OAuth2TokenIntrospectionClaimNames.ACTIVE), "active cannot be null");
|
||||
Assert.isInstanceOf(Boolean.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.ACTIVE), "active must be of type boolean");
|
||||
if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.SCOPE)) {
|
||||
Assert.isInstanceOf(List.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.SCOPE), "scope must be of type List");
|
||||
}
|
||||
if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.EXP)) {
|
||||
Assert.isInstanceOf(Instant.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.EXP), "exp must be of type Instant");
|
||||
}
|
||||
if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.IAT)) {
|
||||
Assert.isInstanceOf(Instant.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.IAT), "iat must be of type Instant");
|
||||
}
|
||||
if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.NBF)) {
|
||||
Assert.isInstanceOf(Instant.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.NBF), "nbf must be of type Instant");
|
||||
}
|
||||
if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.AUD)) {
|
||||
Assert.isInstanceOf(List.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.AUD), "aud must be of type List");
|
||||
}
|
||||
if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.ISS)) {
|
||||
validateURL(this.claims.get(OAuth2TokenIntrospectionClaimNames.ISS), "iss must be a valid URL");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void addClaimToClaimList(String name, String value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
|
||||
((List<String>) this.claims.get(name)).add(value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void acceptClaimValues(String name, Consumer<List<String>> valuesConsumer) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(valuesConsumer, "valuesConsumer cannot be null");
|
||||
this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
|
||||
List<String> values = (List<String>) this.claims.get(name);
|
||||
valuesConsumer.accept(values);
|
||||
}
|
||||
|
||||
private static void validateURL(Object url, String errorMessage) {
|
||||
if (URL.class.isAssignableFrom(url.getClass())) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
new URI(url.toString()).toURL();
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException(errorMessage, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -13,27 +13,40 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization;
|
||||
|
||||
import org.springframework.security.oauth2.server.authorization.Version;
|
||||
import org.springframework.util.Assert;
|
||||
package org.springframework.security.oauth2.core;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Standard token types defined in the OAuth Token Type Hints Registry.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7009#section-4.1.2">4.1.2 OAuth Token Type Hints Registry</a>
|
||||
*/
|
||||
public final class TokenType implements Serializable {
|
||||
public final class OAuth2TokenType implements Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
public static final TokenType ACCESS_TOKEN = new TokenType("access_token");
|
||||
public static final TokenType AUTHORIZATION_CODE = new TokenType("authorization_code");
|
||||
public static final OAuth2TokenType ACCESS_TOKEN = new OAuth2TokenType("access_token");
|
||||
public static final OAuth2TokenType REFRESH_TOKEN = new OAuth2TokenType("refresh_token");
|
||||
private final String value;
|
||||
|
||||
public TokenType(String value) {
|
||||
/**
|
||||
* Constructs an {@code OAuth2TokenType} using the provided value.
|
||||
*
|
||||
* @param value the value of the token type
|
||||
*/
|
||||
public OAuth2TokenType(String value) {
|
||||
Assert.hasText(value, "value cannot be empty");
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the token type.
|
||||
*
|
||||
* @return the value of the token type
|
||||
*/
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
@@ -46,12 +59,12 @@ public final class TokenType implements Serializable {
|
||||
if (obj == null || this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TokenType that = (TokenType) obj;
|
||||
return this.getValue().equals(that.getValue());
|
||||
OAuth2TokenType that = (OAuth2TokenType) obj;
|
||||
return getValue().equals(that.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.getValue().hashCode();
|
||||
return getValue().hashCode();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization;
|
||||
package org.springframework.security.oauth2.core;
|
||||
|
||||
/**
|
||||
* Internal class used for serialization across Spring Security Authorization Server classes.
|
||||
@@ -23,8 +23,8 @@ package org.springframework.security.oauth2.server.authorization;
|
||||
*/
|
||||
public final class Version {
|
||||
private static final int MAJOR = 0;
|
||||
private static final int MINOR = 0;
|
||||
private static final int PATCH = 2;
|
||||
private static final int MINOR = 2;
|
||||
private static final int PATCH = 1;
|
||||
|
||||
/**
|
||||
* Global Serialization value for Spring Security Authorization Server classes.
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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 java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.context.Context;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* A context that holds an {@link Authentication} and (optionally) additional information.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.2.0
|
||||
* @see Context
|
||||
*/
|
||||
public class OAuth2AuthenticationContext implements Context {
|
||||
private final Map<Object, Object> context;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthenticationContext} using the provided parameters.
|
||||
*
|
||||
* @param authentication the {@code Authentication}
|
||||
* @param context a {@code Map} of additional context information
|
||||
*/
|
||||
public OAuth2AuthenticationContext(Authentication authentication, @Nullable Map<Object, Object> context) {
|
||||
Assert.notNull(authentication, "authentication cannot be null");
|
||||
Map<Object, Object> ctx = new HashMap<>();
|
||||
if (!CollectionUtils.isEmpty(context)) {
|
||||
ctx.putAll(context);
|
||||
}
|
||||
ctx.put(Authentication.class, authentication);
|
||||
this.context = Collections.unmodifiableMap(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthenticationContext} using the provided parameters.
|
||||
*
|
||||
* @param context a {@code Map} of context information, must contain the {@code Authentication}
|
||||
* @since 0.2.1
|
||||
*/
|
||||
public OAuth2AuthenticationContext(Map<Object, Object> context) {
|
||||
Assert.notEmpty(context, "context cannot be empty");
|
||||
Assert.notNull(context.get(Authentication.class), "authentication cannot be null");
|
||||
this.context = Collections.unmodifiableMap(new HashMap<>(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Authentication} associated to the context.
|
||||
*
|
||||
* @param <T> the type of the {@code Authentication}
|
||||
* @return the {@link Authentication}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Authentication> T getAuthentication() {
|
||||
return (T) get(Authentication.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
@Override
|
||||
public <V> V get(Object key) {
|
||||
return hasKey(key) ? (V) this.context.get(key) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasKey(Object key) {
|
||||
Assert.notNull(key, "key cannot be null");
|
||||
return this.context.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for subclasses of {@link OAuth2AuthenticationContext}.
|
||||
*
|
||||
* @param <T> the type of the authentication context
|
||||
* @param <B> the type of the builder
|
||||
* @since 0.2.1
|
||||
*/
|
||||
protected static abstract class AbstractBuilder<T extends OAuth2AuthenticationContext, B extends AbstractBuilder<T, B>> {
|
||||
private final Map<Object, Object> context = new HashMap<>();
|
||||
|
||||
protected AbstractBuilder(Authentication authentication) {
|
||||
Assert.notNull(authentication, "authentication cannot be null");
|
||||
put(Authentication.class, authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates an attribute.
|
||||
*
|
||||
* @param key the key for the attribute
|
||||
* @param value the value of the attribute
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B put(Object key, Object value) {
|
||||
Assert.notNull(key, "key cannot be null");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
getContext().put(key, value);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the attributes {@code Map}
|
||||
* allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param contextConsumer a {@link Consumer} of the attributes {@code Map}
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B context(Consumer<Map<Object, Object>> contextConsumer) {
|
||||
contextConsumer.accept(getContext());
|
||||
return getThis();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <V> V get(Object key) {
|
||||
return (V) getContext().get(key);
|
||||
}
|
||||
|
||||
protected Map<Object, Object> getContext() {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final B getThis() {
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link OAuth2AuthenticationContext}.
|
||||
*
|
||||
* @return the {@link OAuth2AuthenticationContext}
|
||||
*/
|
||||
public abstract T build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.context;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A facility for holding information associated to a specific context.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public interface Context {
|
||||
|
||||
/**
|
||||
* Returns the value of the attribute associated to the key.
|
||||
*
|
||||
* @param key the key for the attribute
|
||||
* @param <V> the type of the value for the attribute
|
||||
* @return the value of the attribute associated to the key, or {@code null} if not available
|
||||
*/
|
||||
@Nullable
|
||||
<V> V get(Object key);
|
||||
|
||||
/**
|
||||
* Returns the value of the attribute associated to the key.
|
||||
*
|
||||
* @param key the key for the attribute
|
||||
* @param <V> the type of the value for the attribute
|
||||
* @return the value of the attribute associated to the key, or {@code null} if not available or not of the specified type
|
||||
*/
|
||||
@Nullable
|
||||
default <V> V get(Class<V> key) {
|
||||
Assert.notNull(key, "key cannot be null");
|
||||
V value = get((Object) key);
|
||||
return key.isInstance(value) ? value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if an attribute associated to the key exists, {@code false} otherwise.
|
||||
*
|
||||
* @param key the key for the attribute
|
||||
* @return {@code true} if an attribute associated to the key exists, {@code false} otherwise
|
||||
*/
|
||||
boolean hasKey(Object key);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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.http.converter;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadata;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadataClaimNames;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link HttpMessageConverter} for an {@link OAuth2AuthorizationServerMetadata OAuth 2.0 Authorization Server Metadata Response}.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.1
|
||||
* @see AbstractHttpMessageConverter
|
||||
* @see OAuth2AuthorizationServerMetadata
|
||||
*/
|
||||
public class OAuth2AuthorizationServerMetadataHttpMessageConverter
|
||||
extends AbstractHttpMessageConverter<OAuth2AuthorizationServerMetadata> {
|
||||
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP =
|
||||
new ParameterizedTypeReference<Map<String, Object>>() {};
|
||||
|
||||
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
|
||||
|
||||
private Converter<Map<String, Object>, OAuth2AuthorizationServerMetadata> authorizationServerMetadataConverter = new OAuth2AuthorizationServerMetadataConverter();
|
||||
private Converter<OAuth2AuthorizationServerMetadata, Map<String, Object>> authorizationServerMetadataParametersConverter = OAuth2AuthorizationServerMetadata::getClaims;
|
||||
|
||||
public OAuth2AuthorizationServerMetadataHttpMessageConverter() {
|
||||
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return OAuth2AuthorizationServerMetadata.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected OAuth2AuthorizationServerMetadata readInternal(Class<? extends OAuth2AuthorizationServerMetadata> clazz, HttpInputMessage inputMessage)
|
||||
throws HttpMessageNotReadableException {
|
||||
try {
|
||||
Map<String, Object> authorizationServerMetadataParameters =
|
||||
(Map<String, Object>) this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
|
||||
return this.authorizationServerMetadataConverter.convert(authorizationServerMetadataParameters);
|
||||
} catch (Exception ex) {
|
||||
throw new HttpMessageNotReadableException(
|
||||
"An error occurred reading the OAuth 2.0 Authorization Server Metadata: " + ex.getMessage(), ex, inputMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(OAuth2AuthorizationServerMetadata authorizationServerMetadata, HttpOutputMessage outputMessage)
|
||||
throws HttpMessageNotWritableException {
|
||||
try {
|
||||
Map<String, Object> authorizationServerMetadataResponseParameters =
|
||||
this.authorizationServerMetadataParametersConverter.convert(authorizationServerMetadata);
|
||||
this.jsonMessageConverter.write(
|
||||
authorizationServerMetadataResponseParameters,
|
||||
STRING_OBJECT_MAP.getType(),
|
||||
MediaType.APPLICATION_JSON,
|
||||
outputMessage
|
||||
);
|
||||
} catch (Exception ex) {
|
||||
throw new HttpMessageNotWritableException(
|
||||
"An error occurred writing the OAuth 2.0 Authorization Server Metadata: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the OAuth 2.0 Authorization Server Metadata
|
||||
* parameters to an {@link OAuth2AuthorizationServerMetadata}.
|
||||
*
|
||||
* @param authorizationServerMetadataConverter the {@link Converter} used for converting to
|
||||
* an {@link OAuth2AuthorizationServerMetadata}.
|
||||
*/
|
||||
public final void setAuthorizationServerMetadataConverter(Converter<Map<String, Object>, OAuth2AuthorizationServerMetadata> authorizationServerMetadataConverter) {
|
||||
Assert.notNull(authorizationServerMetadataConverter, "authorizationServerMetadataConverter cannot be null");
|
||||
this.authorizationServerMetadataConverter = authorizationServerMetadataConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the {@link OAuth2AuthorizationServerMetadata} to a
|
||||
* {@code Map} representation of the OAuth 2.0 Authorization Server Metadata.
|
||||
*
|
||||
* @param authorizationServerMetadataParametersConverter the {@link Converter} used for converting to a
|
||||
* {@code Map} representation of the OAuth 2.0 Authorization Server Metadata.
|
||||
*/
|
||||
public final void setAuthorizationServerMetadataParametersConverter(Converter<OAuth2AuthorizationServerMetadata, Map<String, Object>> authorizationServerMetadataParametersConverter) {
|
||||
Assert.notNull(authorizationServerMetadataParametersConverter, "authorizationServerMetadataParametersConverter cannot be null");
|
||||
this.authorizationServerMetadataParametersConverter = authorizationServerMetadataParametersConverter;
|
||||
}
|
||||
|
||||
private static final class OAuth2AuthorizationServerMetadataConverter implements Converter<Map<String, Object>, OAuth2AuthorizationServerMetadata> {
|
||||
private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
|
||||
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
|
||||
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
|
||||
private static final TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class);
|
||||
private final ClaimTypeConverter claimTypeConverter;
|
||||
|
||||
private OAuth2AuthorizationServerMetadataConverter() {
|
||||
Converter<Object, ?> collectionStringConverter = getConverter(
|
||||
TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR));
|
||||
Converter<Object, ?> urlConverter = getConverter(URL_TYPE_DESCRIPTOR);
|
||||
|
||||
Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, urlConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, urlConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, urlConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, urlConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, urlConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT, urlConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, collectionStringConverter);
|
||||
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationServerMetadata convert(Map<String, Object> source) {
|
||||
Map<String, Object> parsedClaims = this.claimTypeConverter.convert(source);
|
||||
return OAuth2AuthorizationServerMetadata.withClaims(parsedClaims).build();
|
||||
}
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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.http.converter;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospection;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link HttpMessageConverter} for an {@link OAuth2TokenIntrospection OAuth 2.0 Token Introspection Response}.
|
||||
*
|
||||
* @author Gerardo Roza
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.1
|
||||
* @see AbstractHttpMessageConverter
|
||||
* @see OAuth2TokenIntrospection
|
||||
*/
|
||||
public class OAuth2TokenIntrospectionHttpMessageConverter extends AbstractHttpMessageConverter<OAuth2TokenIntrospection> {
|
||||
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<Map<String, Object>>() {
|
||||
};
|
||||
|
||||
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
|
||||
|
||||
private Converter<Map<String, Object>, OAuth2TokenIntrospection> tokenIntrospectionConverter = new MapOAuth2TokenIntrospectionConverter();
|
||||
private Converter<OAuth2TokenIntrospection, Map<String, Object>> tokenIntrospectionParametersConverter = new OAuth2TokenIntrospectionMapConverter();
|
||||
|
||||
public OAuth2TokenIntrospectionHttpMessageConverter() {
|
||||
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return OAuth2TokenIntrospection.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected OAuth2TokenIntrospection readInternal(Class<? extends OAuth2TokenIntrospection> clazz, HttpInputMessage inputMessage)
|
||||
throws HttpMessageNotReadableException {
|
||||
try {
|
||||
Map<String, Object> tokenIntrospectionParameters = (Map<String, Object>) this.jsonMessageConverter
|
||||
.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
|
||||
return this.tokenIntrospectionConverter.convert(tokenIntrospectionParameters);
|
||||
} catch (Exception ex) {
|
||||
throw new HttpMessageNotReadableException(
|
||||
"An error occurred reading the Token Introspection Response: " + ex.getMessage(), ex, inputMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(OAuth2TokenIntrospection tokenIntrospection, HttpOutputMessage outputMessage)
|
||||
throws HttpMessageNotWritableException {
|
||||
try {
|
||||
Map<String, Object> tokenIntrospectionResponseParameters = this.tokenIntrospectionParametersConverter
|
||||
.convert(tokenIntrospection);
|
||||
this.jsonMessageConverter.write(tokenIntrospectionResponseParameters, STRING_OBJECT_MAP.getType(),
|
||||
MediaType.APPLICATION_JSON, outputMessage);
|
||||
} catch (Exception ex) {
|
||||
throw new HttpMessageNotWritableException(
|
||||
"An error occurred writing the Token Introspection Response: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the Token Introspection Response parameters to an {@link OAuth2TokenIntrospection}.
|
||||
*
|
||||
* @param tokenIntrospectionConverter the {@link Converter} used for converting to an {@link OAuth2TokenIntrospection}
|
||||
*/
|
||||
public final void setTokenIntrospectionConverter(
|
||||
Converter<Map<String, Object>, OAuth2TokenIntrospection> tokenIntrospectionConverter) {
|
||||
Assert.notNull(tokenIntrospectionConverter, "tokenIntrospectionConverter cannot be null");
|
||||
this.tokenIntrospectionConverter = tokenIntrospectionConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting an {@link OAuth2TokenIntrospection}
|
||||
* to a {@code Map} representation of the Token Introspection Response parameters.
|
||||
*
|
||||
* @param tokenIntrospectionParametersConverter the {@link Converter} used for converting to a
|
||||
* {@code Map} representation of the Token Introspection Response parameters
|
||||
*/
|
||||
public final void setTokenIntrospectionParametersConverter(
|
||||
Converter<OAuth2TokenIntrospection, Map<String, Object>> tokenIntrospectionParametersConverter) {
|
||||
Assert.notNull(tokenIntrospectionParametersConverter, "tokenIntrospectionParametersConverter cannot be null");
|
||||
this.tokenIntrospectionParametersConverter = tokenIntrospectionParametersConverter;
|
||||
}
|
||||
|
||||
private static final class MapOAuth2TokenIntrospectionConverter
|
||||
implements Converter<Map<String, Object>, OAuth2TokenIntrospection> {
|
||||
|
||||
private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
|
||||
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
|
||||
private static final TypeDescriptor BOOLEAN_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Boolean.class);
|
||||
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
|
||||
private static final TypeDescriptor INSTANT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Instant.class);
|
||||
private static final TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class);
|
||||
private final ClaimTypeConverter claimTypeConverter;
|
||||
|
||||
private MapOAuth2TokenIntrospectionConverter() {
|
||||
Converter<Object, ?> booleanConverter = getConverter(BOOLEAN_TYPE_DESCRIPTOR);
|
||||
Converter<Object, ?> stringConverter = getConverter(STRING_TYPE_DESCRIPTOR);
|
||||
Converter<Object, ?> instantConverter = getConverter(INSTANT_TYPE_DESCRIPTOR);
|
||||
Converter<Object, ?> collectionStringConverter = getConverter(
|
||||
TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR));
|
||||
Converter<Object, ?> urlConverter = getConverter(URL_TYPE_DESCRIPTOR);
|
||||
|
||||
Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
|
||||
claimConverters.put(OAuth2TokenIntrospectionClaimNames.ACTIVE, booleanConverter);
|
||||
claimConverters.put(OAuth2TokenIntrospectionClaimNames.SCOPE, MapOAuth2TokenIntrospectionConverter::convertScope);
|
||||
claimConverters.put(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, stringConverter);
|
||||
claimConverters.put(OAuth2TokenIntrospectionClaimNames.USERNAME, stringConverter);
|
||||
claimConverters.put(OAuth2TokenIntrospectionClaimNames.TOKEN_TYPE, stringConverter);
|
||||
claimConverters.put(OAuth2TokenIntrospectionClaimNames.EXP, instantConverter);
|
||||
claimConverters.put(OAuth2TokenIntrospectionClaimNames.IAT, instantConverter);
|
||||
claimConverters.put(OAuth2TokenIntrospectionClaimNames.NBF, instantConverter);
|
||||
claimConverters.put(OAuth2TokenIntrospectionClaimNames.SUB, stringConverter);
|
||||
claimConverters.put(OAuth2TokenIntrospectionClaimNames.AUD, collectionStringConverter);
|
||||
claimConverters.put(OAuth2TokenIntrospectionClaimNames.ISS, urlConverter);
|
||||
claimConverters.put(OAuth2TokenIntrospectionClaimNames.JTI, stringConverter);
|
||||
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2TokenIntrospection convert(Map<String, Object> source) {
|
||||
Map<String, Object> parsedClaims = this.claimTypeConverter.convert(source);
|
||||
return OAuth2TokenIntrospection.withClaims(parsedClaims).build();
|
||||
}
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor);
|
||||
}
|
||||
|
||||
private static List<String> convertScope(Object scope) {
|
||||
if (scope == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Arrays.asList(StringUtils.delimitedListToStringArray(scope.toString(), " "));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class OAuth2TokenIntrospectionMapConverter
|
||||
implements Converter<OAuth2TokenIntrospection, Map<String, Object>> {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> convert(OAuth2TokenIntrospection source) {
|
||||
Map<String, Object> responseClaims = new LinkedHashMap<>(source.getClaims());
|
||||
if (!CollectionUtils.isEmpty(source.getScopes())) {
|
||||
responseClaims.put(OAuth2TokenIntrospectionClaimNames.SCOPE, StringUtils.collectionToDelimitedString(source.getScopes(), " "));
|
||||
}
|
||||
if (source.getExpiresAt() != null) {
|
||||
responseClaims.put(OAuth2TokenIntrospectionClaimNames.EXP, source.getExpiresAt().getEpochSecond());
|
||||
}
|
||||
if (source.getIssuedAt() != null) {
|
||||
responseClaims.put(OAuth2TokenIntrospectionClaimNames.IAT, source.getIssuedAt().getEpochSecond());
|
||||
}
|
||||
if (source.getNotBefore() != null) {
|
||||
responseClaims.put(OAuth2TokenIntrospectionClaimNames.NBF, source.getNotBefore().getEpochSecond());
|
||||
}
|
||||
return responseClaims;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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.oidc;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
|
||||
/**
|
||||
* A {@link ClaimAccessor} for the "claims" that are contained
|
||||
* in the OpenID Client Registration Request and Response.
|
||||
*
|
||||
* @author Ovidiu Popa
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.1
|
||||
* @see ClaimAccessor
|
||||
* @see OidcClientMetadataClaimNames
|
||||
* @see OidcClientRegistration
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata">2. Client Metadata</a>
|
||||
*/
|
||||
public interface OidcClientMetadataClaimAccessor extends ClaimAccessor {
|
||||
|
||||
/**
|
||||
* Returns the Client Identifier {@code (client_id)}.
|
||||
*
|
||||
* @return the Client Identifier
|
||||
*/
|
||||
default String getClientId() {
|
||||
return getClaimAsString(OidcClientMetadataClaimNames.CLIENT_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time at which the Client Identifier was issued {@code (client_id_issued_at)}.
|
||||
*
|
||||
* @return the time at which the Client Identifier was issued
|
||||
*/
|
||||
default Instant getClientIdIssuedAt() {
|
||||
return getClaimAsInstant(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Client Secret {@code (client_secret)}.
|
||||
*
|
||||
* @return the Client Secret
|
||||
*/
|
||||
default String getClientSecret() {
|
||||
return getClaimAsString(OidcClientMetadataClaimNames.CLIENT_SECRET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time at which the {@code client_secret} will expire {@code (client_secret_expires_at)}.
|
||||
*
|
||||
* @return the time at which the {@code client_secret} will expire
|
||||
*/
|
||||
default Instant getClientSecretExpiresAt() {
|
||||
return getClaimAsInstant(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the Client to be presented to the End-User {@code (client_name)}.
|
||||
*
|
||||
* @return the name of the Client to be presented to the End-User
|
||||
*/
|
||||
default String getClientName() {
|
||||
return getClaimAsString(OidcClientMetadataClaimNames.CLIENT_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the redirection {@code URI} values used by the Client {@code (redirect_uris)}.
|
||||
*
|
||||
* @return the redirection {@code URI} values used by the Client
|
||||
*/
|
||||
default List<String> getRedirectUris() {
|
||||
return getClaimAsStringList(OidcClientMetadataClaimNames.REDIRECT_URIS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authentication method used by the Client for the Token Endpoint {@code (token_endpoint_auth_method)}.
|
||||
*
|
||||
* @return the authentication method used by the Client for the Token Endpoint
|
||||
*/
|
||||
default String getTokenEndpointAuthenticationMethod() {
|
||||
return getClaimAsString(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OAuth 2.0 {@code grant_type} values that the Client will restrict itself to using {@code (grant_types)}.
|
||||
*
|
||||
* @return the OAuth 2.0 {@code grant_type} values that the Client will restrict itself to using
|
||||
*/
|
||||
default List<String> getGrantTypes() {
|
||||
return getClaimAsStringList(OidcClientMetadataClaimNames.GRANT_TYPES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OAuth 2.0 {@code response_type} values that the Client will restrict itself to using {@code (response_types)}.
|
||||
*
|
||||
* @return the OAuth 2.0 {@code response_type} values that the Client will restrict itself to using
|
||||
*/
|
||||
default List<String> getResponseTypes() {
|
||||
return getClaimAsStringList(OidcClientMetadataClaimNames.RESPONSE_TYPES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OAuth 2.0 {@code scope} values that the Client will restrict itself to using {@code (scope)}.
|
||||
*
|
||||
* @return the OAuth 2.0 {@code scope} values that the Client will restrict itself to using
|
||||
*/
|
||||
default List<String> getScopes() {
|
||||
return getClaimAsStringList(OidcClientMetadataClaimNames.SCOPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link SignatureAlgorithm JWS} algorithm required for signing the {@link OidcIdToken ID Token} issued to the Client {@code (id_token_signed_response_alg)}.
|
||||
*
|
||||
* @return the {@link SignatureAlgorithm JWS} algorithm required for signing the {@link OidcIdToken ID Token} issued to the Client
|
||||
*/
|
||||
default String getIdTokenSignedResponseAlgorithm() {
|
||||
return getClaimAsString(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Registration Access Token that can be used at the Client Configuration Endpoint.
|
||||
*
|
||||
* @return the Registration Access Token that can be used at the Client Configuration Endpoint
|
||||
* @since 0.2.1
|
||||
*/
|
||||
default String getRegistrationAccessToken() {
|
||||
return getClaimAsString(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used.
|
||||
*
|
||||
* @return the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used
|
||||
* @since 0.2.1
|
||||
*/
|
||||
default URL getRegistrationClientUrl() {
|
||||
return getClaimAsURL(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.oidc;
|
||||
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||
|
||||
/**
|
||||
* The names of the "claims" defined by OpenID Connect Dynamic Client Registration 1.0
|
||||
* that are contained in the OpenID Client Registration Request and Response.
|
||||
*
|
||||
* @author Ovidiu Popa
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.1
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata">2. Client Metadata</a>
|
||||
*/
|
||||
public interface OidcClientMetadataClaimNames {
|
||||
|
||||
/**
|
||||
* {@code client_id} - the Client Identifier
|
||||
*/
|
||||
String CLIENT_ID = "client_id";
|
||||
|
||||
/**
|
||||
* {@code client_id_issued_at} - the time at which the Client Identifier was issued
|
||||
*/
|
||||
String CLIENT_ID_ISSUED_AT = "client_id_issued_at";
|
||||
|
||||
/**
|
||||
* {@code client_secret} - the Client Secret
|
||||
*/
|
||||
String CLIENT_SECRET = "client_secret";
|
||||
|
||||
/**
|
||||
* {@code client_secret_expires_at} - the time at which the {@code client_secret} will expire or 0 if it will not expire
|
||||
*/
|
||||
String CLIENT_SECRET_EXPIRES_AT = "client_secret_expires_at";
|
||||
|
||||
/**
|
||||
* {@code client_name} - the name of the Client to be presented to the End-User
|
||||
*/
|
||||
String CLIENT_NAME = "client_name";
|
||||
|
||||
/**
|
||||
* {@code redirect_uris} - the redirection {@code URI} values used by the Client
|
||||
*/
|
||||
String REDIRECT_URIS = "redirect_uris";
|
||||
|
||||
/**
|
||||
* {@code token_endpoint_auth_method} - the authentication method used by the Client for the Token Endpoint
|
||||
*/
|
||||
String TOKEN_ENDPOINT_AUTH_METHOD = "token_endpoint_auth_method";
|
||||
|
||||
/**
|
||||
* {@code grant_types} - the OAuth 2.0 {@code grant_type} values that the Client will restrict itself to using
|
||||
*/
|
||||
String GRANT_TYPES = "grant_types";
|
||||
|
||||
/**
|
||||
* {@code response_types} - the OAuth 2.0 {@code response_type} values that the Client will restrict itself to using
|
||||
*/
|
||||
String RESPONSE_TYPES = "response_types";
|
||||
|
||||
/**
|
||||
* {@code scope} - a space-separated list of OAuth 2.0 {@code scope} values that the Client will restrict itself to using
|
||||
*/
|
||||
String SCOPE = "scope";
|
||||
|
||||
/**
|
||||
* {@code id_token_signed_response_alg} - the {@link JwsAlgorithm JWS} algorithm required for signing the {@link OidcIdToken ID Token} issued to the Client
|
||||
*/
|
||||
String ID_TOKEN_SIGNED_RESPONSE_ALG = "id_token_signed_response_alg";
|
||||
|
||||
/**
|
||||
* {@code registration_access_token} - the Registration Access Token that can be used at the Client Configuration Endpoint
|
||||
* @since 0.2.1
|
||||
*/
|
||||
String REGISTRATION_ACCESS_TOKEN = "registration_access_token";
|
||||
|
||||
/**
|
||||
* {@code registration_client_uri} - the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used
|
||||
* @since 0.2.1
|
||||
*/
|
||||
String REGISTRATION_CLIENT_URI = "registration_client_uri";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* 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.oidc;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.security.oauth2.core.Version;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A representation of an OpenID Client Registration Request and Response,
|
||||
* which is sent to and returned from the Client Registration Endpoint,
|
||||
* and contains a set of claims about the Client's Registration information.
|
||||
* The claims are defined by the OpenID Connect Dynamic Client Registration 1.0 specification.
|
||||
*
|
||||
* @author Ovidiu Popa
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.1
|
||||
* @see OidcClientMetadataClaimAccessor
|
||||
* @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest">3.1. Client Registration Request</a>
|
||||
* @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse">3.2. Client Registration Response</a>
|
||||
*/
|
||||
public final class OidcClientRegistration implements OidcClientMetadataClaimAccessor, Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
private final Map<String, Object> claims;
|
||||
|
||||
private OidcClientRegistration(Map<String, Object> claims) {
|
||||
Assert.notEmpty(claims, "claims cannot be empty");
|
||||
this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the metadata as claims.
|
||||
*
|
||||
* @return a {@code Map} of the metadata as claims
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getClaims() {
|
||||
return this.claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} with empty claims.
|
||||
*
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} with the provided claims.
|
||||
*
|
||||
* @param claims the claims to initialize the builder
|
||||
*/
|
||||
public static Builder withClaims(Map<String, Object> claims) {
|
||||
Assert.notEmpty(claims, "claims cannot be empty");
|
||||
return new Builder()
|
||||
.claims(c -> c.putAll(claims));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helps configure an {@link OidcClientRegistration}.
|
||||
*/
|
||||
public static class Builder {
|
||||
private final Map<String, Object> claims = new LinkedHashMap<>();
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Client Identifier, REQUIRED.
|
||||
*
|
||||
* @param clientId the Client Identifier
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder clientId(String clientId) {
|
||||
return claim(OidcClientMetadataClaimNames.CLIENT_ID, clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time at which the Client Identifier was issued, OPTIONAL.
|
||||
*
|
||||
* @param clientIdIssuedAt the time at which the Client Identifier was issued
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder clientIdIssuedAt(Instant clientIdIssuedAt) {
|
||||
return claim(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT, clientIdIssuedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Client Secret, OPTIONAL.
|
||||
*
|
||||
* @param clientSecret the Client Secret
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder clientSecret(String clientSecret) {
|
||||
return claim(OidcClientMetadataClaimNames.CLIENT_SECRET, clientSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time at which the {@code client_secret} will expire or {@code null} if it will not expire, REQUIRED if {@code client_secret} was issued.
|
||||
*
|
||||
* @param clientSecretExpiresAt the time at which the {@code client_secret} will expire or {@code null} if it will not expire
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder clientSecretExpiresAt(Instant clientSecretExpiresAt) {
|
||||
return claim(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT, clientSecretExpiresAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the Client to be presented to the End-User, OPTIONAL.
|
||||
*
|
||||
* @param clientName the name of the Client to be presented to the End-User
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder clientName(String clientName) {
|
||||
return claim(OidcClientMetadataClaimNames.CLIENT_NAME, clientName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the redirection {@code URI} used by the Client, REQUIRED.
|
||||
*
|
||||
* @param redirectUri the redirection {@code URI} used by the Client
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder redirectUri(String redirectUri) {
|
||||
addClaimToClaimList(OidcClientMetadataClaimNames.REDIRECT_URIS, redirectUri);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the redirection {@code URI} values used by the Client,
|
||||
* allowing the ability to add, replace, or remove, REQUIRED.
|
||||
*
|
||||
* @param redirectUrisConsumer a {@code Consumer} of the redirection {@code URI} values used by the Client
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder redirectUris(Consumer<List<String>> redirectUrisConsumer) {
|
||||
acceptClaimValues(OidcClientMetadataClaimNames.REDIRECT_URIS, redirectUrisConsumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the authentication method used by the Client for the Token Endpoint, OPTIONAL.
|
||||
*
|
||||
* @param tokenEndpointAuthenticationMethod the authentication method used by the Client for the Token Endpoint
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder tokenEndpointAuthenticationMethod(String tokenEndpointAuthenticationMethod) {
|
||||
return claim(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD, tokenEndpointAuthenticationMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the OAuth 2.0 {@code grant_type} that the Client will restrict itself to using, OPTIONAL.
|
||||
*
|
||||
* @param grantType the OAuth 2.0 {@code grant_type} that the Client will restrict itself to using
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder grantType(String grantType) {
|
||||
addClaimToClaimList(OidcClientMetadataClaimNames.GRANT_TYPES, grantType);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the OAuth 2.0 {@code grant_type} values that the Client will restrict itself to using,
|
||||
* allowing the ability to add, replace, or remove, OPTIONAL.
|
||||
*
|
||||
* @param grantTypesConsumer a {@code Consumer} of the OAuth 2.0 {@code grant_type} values that the Client will restrict itself to using
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder grantTypes(Consumer<List<String>> grantTypesConsumer) {
|
||||
acceptClaimValues(OidcClientMetadataClaimNames.GRANT_TYPES, grantTypesConsumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the OAuth 2.0 {@code response_type} that the Client will restrict itself to using, OPTIONAL.
|
||||
*
|
||||
* @param responseType the OAuth 2.0 {@code response_type} that the Client will restrict itself to using
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder responseType(String responseType) {
|
||||
addClaimToClaimList(OidcClientMetadataClaimNames.RESPONSE_TYPES, responseType);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the OAuth 2.0 {@code response_type} values that the Client will restrict itself to using,
|
||||
* allowing the ability to add, replace, or remove, OPTIONAL.
|
||||
*
|
||||
* @param responseTypesConsumer a {@code Consumer} of the OAuth 2.0 {@code response_type} values that the Client will restrict itself to using
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder responseTypes(Consumer<List<String>> responseTypesConsumer) {
|
||||
acceptClaimValues(OidcClientMetadataClaimNames.RESPONSE_TYPES, responseTypesConsumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the OAuth 2.0 {@code scope} that the Client will restrict itself to using, OPTIONAL.
|
||||
*
|
||||
* @param scope the OAuth 2.0 {@code scope} that the Client will restrict itself to using
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder scope(String scope) {
|
||||
addClaimToClaimList(OidcClientMetadataClaimNames.SCOPE, scope);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the OAuth 2.0 {@code scope} values that the Client will restrict itself to using,
|
||||
* allowing the ability to add, replace, or remove, OPTIONAL.
|
||||
*
|
||||
* @param scopesConsumer a {@code Consumer} of the OAuth 2.0 {@code scope} values that the Client will restrict itself to using
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder scopes(Consumer<List<String>> scopesConsumer) {
|
||||
acceptClaimValues(OidcClientMetadataClaimNames.SCOPE, scopesConsumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link SignatureAlgorithm JWS} algorithm required for signing the {@link OidcIdToken ID Token} issued to the Client, OPTIONAL.
|
||||
*
|
||||
* @param idTokenSignedResponseAlgorithm the {@link SignatureAlgorithm JWS} algorithm required for signing the {@link OidcIdToken ID Token} issued to the Client
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder idTokenSignedResponseAlgorithm(String idTokenSignedResponseAlgorithm) {
|
||||
return claim(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG, idTokenSignedResponseAlgorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Registration Access Token that can be used at the Client Configuration Endpoint, OPTIONAL.
|
||||
*
|
||||
* @param registrationAccessToken the Registration Access Token that can be used at the Client Configuration Endpoint
|
||||
* @return the {@link Builder} for further configuration
|
||||
* @since 0.2.1
|
||||
*/
|
||||
public Builder registrationAccessToken(String registrationAccessToken) {
|
||||
return claim(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN, registrationAccessToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used, OPTIONAL.
|
||||
*
|
||||
* @param registrationClientUrl the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used
|
||||
* @return the {@link Builder} for further configuration
|
||||
* @since 0.2.1
|
||||
*/
|
||||
public Builder registrationClientUrl(String registrationClientUrl) {
|
||||
return claim(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI, registrationClientUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the claim.
|
||||
*
|
||||
* @param name the claim name
|
||||
* @param value the claim value
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder claim(String name, Object value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
this.claims.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to every {@link #claim(String, Object)} declared so far
|
||||
* allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param claimsConsumer a {@code Consumer} of the claims
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
public Builder claims(Consumer<Map<String, Object>> claimsConsumer) {
|
||||
claimsConsumer.accept(this.claims);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the claims and build the {@link OidcClientRegistration}.
|
||||
* <p>
|
||||
* The following claims are REQUIRED:
|
||||
* {@code client_id}, {@code redirect_uris}.
|
||||
*
|
||||
* @return the {@link OidcClientRegistration}
|
||||
*/
|
||||
public OidcClientRegistration build() {
|
||||
validate();
|
||||
return new OidcClientRegistration(this.claims);
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
if (this.claims.get(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT) != null ||
|
||||
this.claims.get(OidcClientMetadataClaimNames.CLIENT_SECRET) != null) {
|
||||
Assert.notNull(this.claims.get(OidcClientMetadataClaimNames.CLIENT_ID), "client_id cannot be null");
|
||||
}
|
||||
if (this.claims.get(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT) != null) {
|
||||
Assert.isInstanceOf(Instant.class, this.claims.get(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT), "client_id_issued_at must be of type Instant");
|
||||
}
|
||||
if (this.claims.get(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT) != null) {
|
||||
Assert.notNull(this.claims.get(OidcClientMetadataClaimNames.CLIENT_SECRET), "client_secret cannot be null");
|
||||
Assert.isInstanceOf(Instant.class, this.claims.get(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT), "client_secret_expires_at must be of type Instant");
|
||||
}
|
||||
Assert.notNull(this.claims.get(OidcClientMetadataClaimNames.REDIRECT_URIS), "redirect_uris cannot be null");
|
||||
Assert.isInstanceOf(List.class, this.claims.get(OidcClientMetadataClaimNames.REDIRECT_URIS), "redirect_uris must be of type List");
|
||||
Assert.notEmpty((List<?>) this.claims.get(OidcClientMetadataClaimNames.REDIRECT_URIS), "redirect_uris cannot be empty");
|
||||
if (this.claims.get(OidcClientMetadataClaimNames.GRANT_TYPES) != null) {
|
||||
Assert.isInstanceOf(List.class, this.claims.get(OidcClientMetadataClaimNames.GRANT_TYPES), "grant_types must be of type List");
|
||||
Assert.notEmpty((List<?>) this.claims.get(OidcClientMetadataClaimNames.GRANT_TYPES), "grant_types cannot be empty");
|
||||
}
|
||||
if (this.claims.get(OidcClientMetadataClaimNames.RESPONSE_TYPES) != null) {
|
||||
Assert.isInstanceOf(List.class, this.claims.get(OidcClientMetadataClaimNames.RESPONSE_TYPES), "response_types must be of type List");
|
||||
Assert.notEmpty((List<?>) this.claims.get(OidcClientMetadataClaimNames.RESPONSE_TYPES), "response_types cannot be empty");
|
||||
}
|
||||
if (this.claims.get(OidcClientMetadataClaimNames.SCOPE) != null) {
|
||||
Assert.isInstanceOf(List.class, this.claims.get(OidcClientMetadataClaimNames.SCOPE), "scope must be of type List");
|
||||
Assert.notEmpty((List<?>) this.claims.get(OidcClientMetadataClaimNames.SCOPE), "scope cannot be empty");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void addClaimToClaimList(String name, String value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
|
||||
((List<String>) this.claims.get(name)).add(value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void acceptClaimValues(String name, Consumer<List<String>> valuesConsumer) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(valuesConsumer, "valuesConsumer cannot be null");
|
||||
this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
|
||||
List<String> values = (List<String>) this.claims.get(name);
|
||||
valuesConsumer.accept(values);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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.oidc;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2AuthorizationServerMetadata;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A representation of an OpenID Provider Configuration Response,
|
||||
* which is returned from an Issuer's Discovery Endpoint,
|
||||
* and contains a set of claims about the OpenID Provider's configuration.
|
||||
* The claims are defined by the OpenID Connect Discovery 1.0 specification.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.0
|
||||
* @see AbstractOAuth2AuthorizationServerMetadata
|
||||
* @see OidcProviderMetadataClaimAccessor
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">4.2. OpenID Provider Configuration Response</a>
|
||||
*/
|
||||
public final class OidcProviderConfiguration extends AbstractOAuth2AuthorizationServerMetadata
|
||||
implements OidcProviderMetadataClaimAccessor {
|
||||
|
||||
private OidcProviderConfiguration(Map<String, Object> claims) {
|
||||
super(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} with empty claims.
|
||||
*
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} with the provided claims.
|
||||
*
|
||||
* @param claims the claims to initialize the builder
|
||||
*/
|
||||
public static Builder withClaims(Map<String, Object> claims) {
|
||||
Assert.notEmpty(claims, "claims cannot be empty");
|
||||
return new Builder()
|
||||
.claims(c -> c.putAll(claims));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helps configure an {@link OidcProviderConfiguration}.
|
||||
*/
|
||||
public static class Builder extends AbstractBuilder<OidcProviderConfiguration, Builder> {
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this Subject Type to the collection of {@code subject_types_supported} in the resulting
|
||||
* {@link OidcProviderConfiguration}, REQUIRED.
|
||||
*
|
||||
* @param subjectType the Subject Type that the OpenID Provider supports
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder subjectType(String subjectType) {
|
||||
addClaimToClaimList(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, subjectType);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the Subject Types(s) allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param subjectTypesConsumer a {@code Consumer} of the Subject Types(s)
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder subjectTypes(Consumer<List<String>> subjectTypesConsumer) {
|
||||
acceptClaimValues(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, subjectTypesConsumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this {@link JwsAlgorithm JWS} signing algorithm to the collection of {@code id_token_signing_alg_values_supported}
|
||||
* in the resulting {@link OidcProviderConfiguration}, REQUIRED.
|
||||
*
|
||||
* @param signingAlgorithm the {@link JwsAlgorithm JWS} signing algorithm supported for the {@link OidcIdToken ID Token}
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder idTokenSigningAlgorithm(String signingAlgorithm) {
|
||||
addClaimToClaimList(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, signingAlgorithm);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the {@link JwsAlgorithm JWS} signing algorithms for the {@link OidcIdToken ID Token}
|
||||
* allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param signingAlgorithmsConsumer a {@code Consumer} of the {@link JwsAlgorithm JWS} signing algorithms for the {@link OidcIdToken ID Token}
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder idTokenSigningAlgorithms(Consumer<List<String>> signingAlgorithmsConsumer) {
|
||||
acceptClaimValues(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, signingAlgorithmsConsumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the claims and build the {@link OidcProviderConfiguration}.
|
||||
* <p>
|
||||
* The following claims are REQUIRED:
|
||||
* {@code issuer}, {@code authorization_endpoint}, {@code token_endpoint}, {@code jwks_uri},
|
||||
* {@code response_types_supported}, {@code subject_types_supported} and
|
||||
* {@code id_token_signing_alg_values_supported}.
|
||||
*
|
||||
* @return the {@link OidcProviderConfiguration}
|
||||
*/
|
||||
@Override
|
||||
public OidcProviderConfiguration build() {
|
||||
validate();
|
||||
return new OidcProviderConfiguration(getClaims());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validate() {
|
||||
super.validate();
|
||||
Assert.notNull(getClaims().get(OidcProviderMetadataClaimNames.JWKS_URI), "jwksUri cannot be null");
|
||||
Assert.notNull(getClaims().get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be null");
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be empty");
|
||||
Assert.notNull(getClaims().get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms cannot be null");
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms cannot be empty");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void addClaimToClaimList(String name, String value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
getClaims().computeIfAbsent(name, k -> new LinkedList<String>());
|
||||
((List<String>) getClaims().get(name)).add(value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void acceptClaimValues(String name, Consumer<List<String>> valuesConsumer) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(valuesConsumer, "valuesConsumer cannot be null");
|
||||
getClaims().computeIfAbsent(name, k -> new LinkedList<String>());
|
||||
List<String> values = (List<String>) getClaims().get(name);
|
||||
valuesConsumer.accept(values);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.oidc;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadataClaimAccessor;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
/**
|
||||
* A {@link ClaimAccessor} for the "claims" that can be returned
|
||||
* in the OpenID Provider Configuration Response.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.0
|
||||
* @see ClaimAccessor
|
||||
* @see OAuth2AuthorizationServerMetadataClaimAccessor
|
||||
* @see OidcProviderMetadataClaimNames
|
||||
* @see OidcProviderConfiguration
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
|
||||
*/
|
||||
public interface OidcProviderMetadataClaimAccessor extends OAuth2AuthorizationServerMetadataClaimAccessor {
|
||||
|
||||
/**
|
||||
* Returns the Subject Identifier types supported {@code (subject_types_supported)}.
|
||||
*
|
||||
* @return the Subject Identifier types supported
|
||||
*/
|
||||
default List<String> getSubjectTypes() {
|
||||
return getClaimAsStringList(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link JwsAlgorithm JWS} signing algorithms supported for the {@link OidcIdToken ID Token}
|
||||
* to encode the claims in a {@link Jwt} {@code (id_token_signing_alg_values_supported)}.
|
||||
*
|
||||
* @return the {@link JwsAlgorithm JWS} signing algorithms supported for the {@link OidcIdToken ID Token}
|
||||
*/
|
||||
default List<String> getIdTokenSigningAlgorithms() {
|
||||
return getClaimAsStringList(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.oidc;
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadataClaimNames;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||
|
||||
/**
|
||||
* The names of the "claims" defined by OpenID Connect Discovery 1.0 that can be returned
|
||||
* in the OpenID Provider Configuration Response.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.0
|
||||
* @see OAuth2AuthorizationServerMetadataClaimNames
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
|
||||
*/
|
||||
public interface OidcProviderMetadataClaimNames extends OAuth2AuthorizationServerMetadataClaimNames {
|
||||
|
||||
/**
|
||||
* {@code subject_types_supported} - the Subject Identifier types supported
|
||||
*/
|
||||
String SUBJECT_TYPES_SUPPORTED = "subject_types_supported";
|
||||
|
||||
/**
|
||||
* {@code id_token_signing_alg_values_supported} - the {@link JwsAlgorithm JWS} signing algorithms supported for the {@link OidcIdToken ID Token}
|
||||
*/
|
||||
String ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED = "id_token_signing_alg_values_supported";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* 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.oidc.http.converter;
|
||||
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.GsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* This class is a straight copy from Spring Security.
|
||||
* It should be consolidated when merging this codebase into Spring Security.
|
||||
*
|
||||
* Utility methods for {@link HttpMessageConverter}'s.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.1
|
||||
*/
|
||||
final class HttpMessageConverters {
|
||||
|
||||
private static final boolean jackson2Present;
|
||||
|
||||
private static final boolean gsonPresent;
|
||||
|
||||
private static final boolean jsonbPresent;
|
||||
|
||||
static {
|
||||
ClassLoader classLoader = HttpMessageConverters.class.getClassLoader();
|
||||
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
|
||||
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
|
||||
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
|
||||
jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
|
||||
}
|
||||
|
||||
private HttpMessageConverters() {
|
||||
}
|
||||
|
||||
static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
|
||||
if (jackson2Present) {
|
||||
return new MappingJackson2HttpMessageConverter();
|
||||
}
|
||||
if (gsonPresent) {
|
||||
return new GsonHttpMessageConverter();
|
||||
}
|
||||
if (jsonbPresent) {
|
||||
return new JsonbHttpMessageConverter();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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.oidc.http.converter;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcClientMetadataClaimNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link HttpMessageConverter} for an {@link OidcClientRegistration OpenID Client Registration Request and Response}.
|
||||
*
|
||||
* @author Ovidiu Popa
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.1
|
||||
* @see AbstractHttpMessageConverter
|
||||
* @see OidcClientRegistration
|
||||
*/
|
||||
public class OidcClientRegistrationHttpMessageConverter extends AbstractHttpMessageConverter<OidcClientRegistration> {
|
||||
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<Map<String, Object>>() {
|
||||
};
|
||||
|
||||
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
|
||||
|
||||
private Converter<Map<String, Object>, OidcClientRegistration> clientRegistrationConverter = new MapOidcClientRegistrationConverter();
|
||||
private Converter<OidcClientRegistration, Map<String, Object>> clientRegistrationParametersConverter = new OidcClientRegistrationMapConverter();
|
||||
|
||||
public OidcClientRegistrationHttpMessageConverter() {
|
||||
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return OidcClientRegistration.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected OidcClientRegistration readInternal(Class<? extends OidcClientRegistration> clazz, HttpInputMessage inputMessage)
|
||||
throws HttpMessageNotReadableException {
|
||||
try {
|
||||
Map<String, Object> clientRegistrationParameters = (Map<String, Object>) this.jsonMessageConverter
|
||||
.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
|
||||
return this.clientRegistrationConverter.convert(clientRegistrationParameters);
|
||||
} catch (Exception ex) {
|
||||
throw new HttpMessageNotReadableException(
|
||||
"An error occurred reading the OpenID Client Registration: " + ex.getMessage(), ex, inputMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(OidcClientRegistration clientRegistration, HttpOutputMessage outputMessage)
|
||||
throws HttpMessageNotWritableException {
|
||||
try {
|
||||
Map<String, Object> clientRegistrationParameters = this.clientRegistrationParametersConverter
|
||||
.convert(clientRegistration);
|
||||
this.jsonMessageConverter.write(clientRegistrationParameters, STRING_OBJECT_MAP.getType(),
|
||||
MediaType.APPLICATION_JSON, outputMessage);
|
||||
} catch (Exception ex) {
|
||||
throw new HttpMessageNotWritableException(
|
||||
"An error occurred writing the OpenID Client Registration: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the OpenID Client Registration parameters to an {@link OidcClientRegistration}.
|
||||
*
|
||||
* @param clientRegistrationConverter the {@link Converter} used for converting to an {@link OidcClientRegistration}
|
||||
*/
|
||||
public final void setClientRegistrationConverter(
|
||||
Converter<Map<String, Object>, OidcClientRegistration> clientRegistrationConverter) {
|
||||
Assert.notNull(clientRegistrationConverter, "clientRegistrationConverter cannot be null");
|
||||
this.clientRegistrationConverter = clientRegistrationConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the {@link OidcClientRegistration}
|
||||
* to a {@code Map} representation of the OpenID Client Registration parameters.
|
||||
*
|
||||
* @param clientRegistrationParametersConverter the {@link Converter} used for converting to a
|
||||
* {@code Map} representation of the OpenID Client Registration parameters
|
||||
*/
|
||||
public final void setClientRegistrationParametersConverter(
|
||||
Converter<OidcClientRegistration, Map<String, Object>> clientRegistrationParametersConverter) {
|
||||
Assert.notNull(clientRegistrationParametersConverter, "clientRegistrationParametersConverter cannot be null");
|
||||
this.clientRegistrationParametersConverter = clientRegistrationParametersConverter;
|
||||
}
|
||||
|
||||
private static final class MapOidcClientRegistrationConverter
|
||||
implements Converter<Map<String, Object>, OidcClientRegistration> {
|
||||
|
||||
private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
|
||||
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
|
||||
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
|
||||
private static final TypeDescriptor INSTANT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Instant.class);
|
||||
private static final Converter<Object, ?> INSTANT_CONVERTER = getConverter(INSTANT_TYPE_DESCRIPTOR);
|
||||
private final ClaimTypeConverter claimTypeConverter;
|
||||
|
||||
private MapOidcClientRegistrationConverter() {
|
||||
Converter<Object, ?> stringConverter = getConverter(STRING_TYPE_DESCRIPTOR);
|
||||
Converter<Object, ?> collectionStringConverter = getConverter(
|
||||
TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR));
|
||||
|
||||
Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
|
||||
claimConverters.put(OidcClientMetadataClaimNames.CLIENT_ID, stringConverter);
|
||||
claimConverters.put(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT, INSTANT_CONVERTER);
|
||||
claimConverters.put(OidcClientMetadataClaimNames.CLIENT_SECRET, stringConverter);
|
||||
claimConverters.put(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT, MapOidcClientRegistrationConverter::convertClientSecretExpiresAt);
|
||||
claimConverters.put(OidcClientMetadataClaimNames.CLIENT_NAME, stringConverter);
|
||||
claimConverters.put(OidcClientMetadataClaimNames.REDIRECT_URIS, collectionStringConverter);
|
||||
claimConverters.put(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD, stringConverter);
|
||||
claimConverters.put(OidcClientMetadataClaimNames.GRANT_TYPES, collectionStringConverter);
|
||||
claimConverters.put(OidcClientMetadataClaimNames.RESPONSE_TYPES, collectionStringConverter);
|
||||
claimConverters.put(OidcClientMetadataClaimNames.SCOPE, MapOidcClientRegistrationConverter::convertScope);
|
||||
claimConverters.put(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG, stringConverter);
|
||||
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OidcClientRegistration convert(Map<String, Object> source) {
|
||||
Map<String, Object> parsedClaims = this.claimTypeConverter.convert(source);
|
||||
Object clientSecretExpiresAt = parsedClaims.get(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT);
|
||||
if (clientSecretExpiresAt instanceof Number && clientSecretExpiresAt.equals(0)) {
|
||||
parsedClaims.remove(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT);
|
||||
}
|
||||
return OidcClientRegistration.withClaims(parsedClaims).build();
|
||||
}
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
return source -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor);
|
||||
}
|
||||
|
||||
private static Instant convertClientSecretExpiresAt(Object clientSecretExpiresAt) {
|
||||
if (clientSecretExpiresAt != null && String.valueOf(clientSecretExpiresAt).equals("0")) {
|
||||
// 0 indicates that client_secret_expires_at does not expire
|
||||
return null;
|
||||
}
|
||||
return (Instant) INSTANT_CONVERTER.convert(clientSecretExpiresAt);
|
||||
}
|
||||
|
||||
private static List<String> convertScope(Object scope) {
|
||||
if (scope == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Arrays.asList(StringUtils.delimitedListToStringArray(scope.toString(), " "));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class OidcClientRegistrationMapConverter
|
||||
implements Converter<OidcClientRegistration, Map<String, Object>> {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> convert(OidcClientRegistration source) {
|
||||
Map<String, Object> responseClaims = new LinkedHashMap<>(source.getClaims());
|
||||
if (source.getClientIdIssuedAt() != null) {
|
||||
responseClaims.put(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT, source.getClientIdIssuedAt().getEpochSecond());
|
||||
}
|
||||
if (source.getClientSecret() != null) {
|
||||
long clientSecretExpiresAt = 0;
|
||||
if (source.getClientSecretExpiresAt() != null) {
|
||||
clientSecretExpiresAt = source.getClientSecretExpiresAt().getEpochSecond();
|
||||
}
|
||||
responseClaims.put(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT, clientSecretExpiresAt);
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(source.getScopes())) {
|
||||
responseClaims.put(OidcClientMetadataClaimNames.SCOPE, StringUtils.collectionToDelimitedString(source.getScopes(), " "));
|
||||
}
|
||||
return responseClaims;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.core.oidc.http.converter;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcProviderConfiguration;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcProviderMetadataClaimNames;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A {@link HttpMessageConverter} for an {@link OidcProviderConfiguration OpenID Provider Configuration Response}.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.0
|
||||
* @see AbstractHttpMessageConverter
|
||||
* @see OidcProviderConfiguration
|
||||
*/
|
||||
public class OidcProviderConfigurationHttpMessageConverter
|
||||
extends AbstractHttpMessageConverter<OidcProviderConfiguration> {
|
||||
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP =
|
||||
new ParameterizedTypeReference<Map<String, Object>>() {};
|
||||
|
||||
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
|
||||
|
||||
private Converter<Map<String, Object>, OidcProviderConfiguration> providerConfigurationConverter = new OidcProviderConfigurationConverter();
|
||||
private Converter<OidcProviderConfiguration, Map<String, Object>> providerConfigurationParametersConverter = OidcProviderConfiguration::getClaims;
|
||||
|
||||
public OidcProviderConfigurationHttpMessageConverter() {
|
||||
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return OidcProviderConfiguration.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected OidcProviderConfiguration readInternal(Class<? extends OidcProviderConfiguration> clazz, HttpInputMessage inputMessage)
|
||||
throws HttpMessageNotReadableException {
|
||||
try {
|
||||
Map<String, Object> providerConfigurationParameters =
|
||||
(Map<String, Object>) this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
|
||||
return this.providerConfigurationConverter.convert(providerConfigurationParameters);
|
||||
} catch (Exception ex) {
|
||||
throw new HttpMessageNotReadableException(
|
||||
"An error occurred reading the OpenID Provider Configuration: " + ex.getMessage(), ex, inputMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(OidcProviderConfiguration providerConfiguration, HttpOutputMessage outputMessage)
|
||||
throws HttpMessageNotWritableException {
|
||||
try {
|
||||
Map<String, Object> providerConfigurationResponseParameters =
|
||||
this.providerConfigurationParametersConverter.convert(providerConfiguration);
|
||||
this.jsonMessageConverter.write(
|
||||
providerConfigurationResponseParameters,
|
||||
STRING_OBJECT_MAP.getType(),
|
||||
MediaType.APPLICATION_JSON,
|
||||
outputMessage
|
||||
);
|
||||
} catch (Exception ex) {
|
||||
throw new HttpMessageNotWritableException(
|
||||
"An error occurred writing the OpenID Provider Configuration: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the OpenID Provider Configuration parameters
|
||||
* to an {@link OidcProviderConfiguration}.
|
||||
*
|
||||
* @param providerConfigurationConverter the {@link Converter} used for converting to an
|
||||
* {@link OidcProviderConfiguration}
|
||||
*/
|
||||
public final void setProviderConfigurationConverter(Converter<Map<String, Object>, OidcProviderConfiguration> providerConfigurationConverter) {
|
||||
Assert.notNull(providerConfigurationConverter, "providerConfigurationConverter cannot be null");
|
||||
this.providerConfigurationConverter = providerConfigurationConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the {@link OidcProviderConfiguration} to a
|
||||
* {@code Map} representation of the OpenID Provider Configuration.
|
||||
*
|
||||
* @param providerConfigurationParametersConverter the {@link Converter} used for converting to a
|
||||
* {@code Map} representation of the OpenID Provider Configuration
|
||||
*/
|
||||
public final void setProviderConfigurationParametersConverter(
|
||||
Converter<OidcProviderConfiguration, Map<String, Object>> providerConfigurationParametersConverter) {
|
||||
Assert.notNull(providerConfigurationParametersConverter, "providerConfigurationParametersConverter cannot be null");
|
||||
this.providerConfigurationParametersConverter = providerConfigurationParametersConverter;
|
||||
}
|
||||
|
||||
private static final class OidcProviderConfigurationConverter implements Converter<Map<String, Object>, OidcProviderConfiguration> {
|
||||
private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
|
||||
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
|
||||
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
|
||||
private static final TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class);
|
||||
private final ClaimTypeConverter claimTypeConverter;
|
||||
|
||||
private OidcProviderConfigurationConverter() {
|
||||
Converter<Object, ?> collectionStringConverter = getConverter(
|
||||
TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR));
|
||||
Converter<Object, ?> urlConverter = getConverter(URL_TYPE_DESCRIPTOR);
|
||||
|
||||
Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
|
||||
claimConverters.put(OidcProviderMetadataClaimNames.ISSUER, urlConverter);
|
||||
claimConverters.put(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT, urlConverter);
|
||||
claimConverters.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT, urlConverter);
|
||||
claimConverters.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OidcProviderMetadataClaimNames.JWKS_URI, urlConverter);
|
||||
claimConverters.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, collectionStringConverter);
|
||||
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OidcProviderConfiguration convert(Map<String, Object> source) {
|
||||
Map<String, Object> parsedClaims = this.claimTypeConverter.convert(source);
|
||||
return OidcProviderConfiguration.withClaims(parsedClaims).build();
|
||||
}
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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.oidc.http.converter;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link HttpMessageConverter} for an {@link OidcUserInfo OpenID Connect UserInfo Response}.
|
||||
*
|
||||
* @author Ido Salomon
|
||||
* @author Steve Riesenberg
|
||||
* @since 0.2.1
|
||||
* @see AbstractHttpMessageConverter
|
||||
* @see OidcUserInfo
|
||||
*/
|
||||
public class OidcUserInfoHttpMessageConverter extends AbstractHttpMessageConverter<OidcUserInfo> {
|
||||
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP =
|
||||
new ParameterizedTypeReference<Map<String, Object>>() {};
|
||||
|
||||
private final GenericHttpMessageConverter<Object> jsonMessageConverter =
|
||||
HttpMessageConverters.getJsonMessageConverter();
|
||||
|
||||
private Converter<Map<String, Object>, OidcUserInfo> userInfoConverter = new MapOidcUserInfoConverter();
|
||||
private Converter<OidcUserInfo, Map<String, Object>> userInfoParametersConverter = OidcUserInfo::getClaims;
|
||||
|
||||
public OidcUserInfoHttpMessageConverter() {
|
||||
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return OidcUserInfo.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected OidcUserInfo readInternal(Class<? extends OidcUserInfo> clazz, HttpInputMessage inputMessage)
|
||||
throws HttpMessageNotReadableException {
|
||||
try {
|
||||
Map<String, Object> userInfoParameters =
|
||||
(Map<String, Object>) this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
|
||||
return this.userInfoConverter.convert(userInfoParameters);
|
||||
} catch (Exception ex) {
|
||||
throw new HttpMessageNotReadableException(
|
||||
"An error occurred reading the UserInfo response: " + ex.getMessage(), ex, inputMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(OidcUserInfo oidcUserInfo, HttpOutputMessage outputMessage)
|
||||
throws HttpMessageNotWritableException {
|
||||
try {
|
||||
Map<String, Object> userInfoResponseParameters =
|
||||
this.userInfoParametersConverter.convert(oidcUserInfo);
|
||||
this.jsonMessageConverter.write(
|
||||
userInfoResponseParameters,
|
||||
STRING_OBJECT_MAP.getType(),
|
||||
MediaType.APPLICATION_JSON,
|
||||
outputMessage
|
||||
);
|
||||
} catch (Exception ex) {
|
||||
throw new HttpMessageNotWritableException(
|
||||
"An error occurred writing the UserInfo response: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the UserInfo parameters
|
||||
* to an {@link OidcUserInfo}.
|
||||
*
|
||||
* @param userInfoConverter the {@link Converter} used for converting to an {@link OidcUserInfo}
|
||||
*/
|
||||
public final void setUserInfoConverter(Converter<Map<String, Object>, OidcUserInfo> userInfoConverter) {
|
||||
Assert.notNull(userInfoConverter, "userInfoConverter cannot be null");
|
||||
this.userInfoConverter = userInfoConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the {@link OidcUserInfo} to a
|
||||
* {@code Map} representation of the UserInfo.
|
||||
*
|
||||
* @param userInfoParametersConverter the {@link Converter} used for converting to a
|
||||
* {@code Map} representation of the UserInfo
|
||||
*/
|
||||
public final void setUserInfoParametersConverter(
|
||||
Converter<OidcUserInfo, Map<String, Object>> userInfoParametersConverter) {
|
||||
Assert.notNull(userInfoParametersConverter, "userInfoParametersConverter cannot be null");
|
||||
this.userInfoParametersConverter = userInfoParametersConverter;
|
||||
}
|
||||
|
||||
private static final class MapOidcUserInfoConverter implements Converter<Map<String, Object>, OidcUserInfo> {
|
||||
private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
|
||||
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
|
||||
private static final TypeDescriptor BOOLEAN_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Boolean.class);
|
||||
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
|
||||
private static final TypeDescriptor INSTANT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Instant.class);
|
||||
private static final TypeDescriptor STRING_OBJECT_MAP_DESCRIPTOR = TypeDescriptor.map(Map.class, STRING_TYPE_DESCRIPTOR, OBJECT_TYPE_DESCRIPTOR);
|
||||
private final ClaimTypeConverter claimTypeConverter;
|
||||
|
||||
private MapOidcUserInfoConverter() {
|
||||
Converter<Object, ?> booleanConverter = getConverter(BOOLEAN_TYPE_DESCRIPTOR);
|
||||
Converter<Object, ?> stringConverter = getConverter(STRING_TYPE_DESCRIPTOR);
|
||||
Converter<Object, ?> instantConverter = getConverter(INSTANT_TYPE_DESCRIPTOR);
|
||||
Converter<Object, ?> mapConverter = getConverter(STRING_OBJECT_MAP_DESCRIPTOR);
|
||||
|
||||
Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
|
||||
claimConverters.put(StandardClaimNames.SUB, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.NAME, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.GIVEN_NAME, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.FAMILY_NAME, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.MIDDLE_NAME, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.NICKNAME, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.PREFERRED_USERNAME, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.PROFILE, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.PICTURE, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.WEBSITE, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.EMAIL, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
|
||||
claimConverters.put(StandardClaimNames.GENDER, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.BIRTHDATE, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.ZONEINFO, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.LOCALE, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.PHONE_NUMBER, stringConverter);
|
||||
claimConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
|
||||
claimConverters.put(StandardClaimNames.ADDRESS, mapConverter);
|
||||
claimConverters.put(StandardClaimNames.UPDATED_AT, instantConverter);
|
||||
|
||||
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OidcUserInfo convert(Map<String, Object> source) {
|
||||
Map<String, Object> parsedClaims = this.claimTypeConverter.convert(source);
|
||||
return new OidcUserInfo(parsedClaims);
|
||||
}
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.jose.jws;
|
||||
|
||||
import com.nimbusds.jose.JOSEException;
|
||||
import com.nimbusds.jose.JOSEObjectType;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
import com.nimbusds.jose.JWSSigner;
|
||||
import com.nimbusds.jose.KeyLengthException;
|
||||
import com.nimbusds.jose.crypto.MACSigner;
|
||||
import com.nimbusds.jose.crypto.RSASSASigner;
|
||||
import com.nimbusds.jose.jwk.JWK;
|
||||
import com.nimbusds.jose.util.Base64;
|
||||
import com.nimbusds.jose.util.Base64URL;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.crypto.keys.KeyManager;
|
||||
import org.springframework.security.crypto.keys.ManagedKey;
|
||||
import org.springframework.security.oauth2.jose.JoseHeader;
|
||||
import org.springframework.security.oauth2.jose.JoseHeaderNames;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncodingException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.security.PrivateKey;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link JwtEncoder} that encodes a JSON Web Token (JWT)
|
||||
* using the JSON Web Signature (JWS) Compact Serialization format.
|
||||
* The private/secret key used for signing the JWS is obtained
|
||||
* from the {@link KeyManager} supplied via the constructor.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This implementation uses the Nimbus JOSE + JWT SDK.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
* @see JwtEncoder
|
||||
* @see KeyManager
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token (JWT)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515">JSON Web Signature (JWS)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515#section-3.1">JWS Compact Serialization</a>
|
||||
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-jose-jwt">Nimbus JOSE + JWT SDK</a>
|
||||
*/
|
||||
public final class NimbusJwsEncoder implements JwtEncoder {
|
||||
private static final String ENCODING_ERROR_MESSAGE_TEMPLATE =
|
||||
"An error occurred while attempting to encode the Jwt: %s";
|
||||
private static final String RSA_KEY_TYPE = "RSA";
|
||||
private static final String EC_KEY_TYPE = "EC";
|
||||
private static final Map<JwsAlgorithm, String> jcaKeyAlgorithmMappings = new HashMap<JwsAlgorithm, String>() {
|
||||
{
|
||||
put(MacAlgorithm.HS256, "HmacSHA256");
|
||||
put(MacAlgorithm.HS384, "HmacSHA384");
|
||||
put(MacAlgorithm.HS512, "HmacSHA512");
|
||||
put(SignatureAlgorithm.RS256, RSA_KEY_TYPE);
|
||||
put(SignatureAlgorithm.RS384, RSA_KEY_TYPE);
|
||||
put(SignatureAlgorithm.RS512, RSA_KEY_TYPE);
|
||||
put(SignatureAlgorithm.ES256, EC_KEY_TYPE);
|
||||
put(SignatureAlgorithm.ES384, EC_KEY_TYPE);
|
||||
put(SignatureAlgorithm.ES512, EC_KEY_TYPE);
|
||||
}
|
||||
};
|
||||
private static final Converter<JoseHeader, JWSHeader> jwsHeaderConverter = new JwsHeaderConverter();
|
||||
private static final Converter<JwtClaimsSet, JWTClaimsSet> jwtClaimsSetConverter = new JwtClaimsSetConverter();
|
||||
private final KeyManager keyManager;
|
||||
|
||||
/**
|
||||
* Constructs a {@code NimbusJwsEncoder} using the provided parameters.
|
||||
*
|
||||
* @param keyManager the key manager
|
||||
*/
|
||||
public NimbusJwsEncoder(KeyManager keyManager) {
|
||||
Assert.notNull(keyManager, "keyManager cannot be null");
|
||||
this.keyManager = keyManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Jwt encode(JoseHeader headers, JwtClaimsSet claims) throws JwtEncodingException {
|
||||
Assert.notNull(headers, "headers cannot be null");
|
||||
Assert.notNull(claims, "claims cannot be null");
|
||||
|
||||
ManagedKey managedKey = selectKey(headers);
|
||||
if (managedKey == null) {
|
||||
throw new JwtEncodingException(String.format(
|
||||
ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||
"Unsupported key for algorithm '" + headers.getJwsAlgorithm().getName() + "'"));
|
||||
}
|
||||
|
||||
JWSSigner jwsSigner;
|
||||
if (managedKey.isAsymmetric()) {
|
||||
if (!managedKey.getAlgorithm().equals(RSA_KEY_TYPE)) {
|
||||
throw new JwtEncodingException(String.format(
|
||||
ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||
"Unsupported key type '" + managedKey.getAlgorithm() + "'"));
|
||||
}
|
||||
PrivateKey privateKey = managedKey.getKey();
|
||||
jwsSigner = new RSASSASigner(privateKey);
|
||||
} else {
|
||||
SecretKey secretKey = managedKey.getKey();
|
||||
try {
|
||||
jwsSigner = new MACSigner(secretKey);
|
||||
} catch (KeyLengthException ex) {
|
||||
throw new JwtEncodingException(String.format(
|
||||
ENCODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
headers = JoseHeader.from(headers)
|
||||
.type(JOSEObjectType.JWT.getType())
|
||||
.keyId(managedKey.getKeyId())
|
||||
.build();
|
||||
JWSHeader jwsHeader = jwsHeaderConverter.convert(headers);
|
||||
|
||||
claims = JwtClaimsSet.from(claims)
|
||||
.id(UUID.randomUUID().toString())
|
||||
.build();
|
||||
JWTClaimsSet jwtClaimsSet = jwtClaimsSetConverter.convert(claims);
|
||||
|
||||
SignedJWT signedJWT = new SignedJWT(jwsHeader, jwtClaimsSet);
|
||||
try {
|
||||
signedJWT.sign(jwsSigner);
|
||||
} catch (JOSEException ex) {
|
||||
throw new JwtEncodingException(String.format(
|
||||
ENCODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
|
||||
}
|
||||
String jws = signedJWT.serialize();
|
||||
|
||||
return new Jwt(jws, claims.getIssuedAt(), claims.getExpiresAt(),
|
||||
headers.getHeaders(), claims.getClaims());
|
||||
}
|
||||
|
||||
private ManagedKey selectKey(JoseHeader headers) {
|
||||
JwsAlgorithm jwsAlgorithm = headers.getJwsAlgorithm();
|
||||
String keyAlgorithm = jcaKeyAlgorithmMappings.get(jwsAlgorithm);
|
||||
if (!StringUtils.hasText(keyAlgorithm)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<ManagedKey> matchingKeys = this.keyManager.findByAlgorithm(keyAlgorithm);
|
||||
if (CollectionUtils.isEmpty(matchingKeys)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return matchingKeys.stream()
|
||||
.filter(ManagedKey::isActive)
|
||||
.max(this::mostRecentActivated)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private int mostRecentActivated(ManagedKey managedKey1, ManagedKey managedKey2) {
|
||||
return managedKey1.getActivatedOn().isAfter(managedKey2.getActivatedOn()) ? 1 : -1;
|
||||
}
|
||||
|
||||
private static class JwsHeaderConverter implements Converter<JoseHeader, JWSHeader> {
|
||||
|
||||
@Override
|
||||
public JWSHeader convert(JoseHeader headers) {
|
||||
JWSHeader.Builder builder = new JWSHeader.Builder(
|
||||
JWSAlgorithm.parse(headers.getJwsAlgorithm().getName()));
|
||||
|
||||
Set<String> critical = headers.getCritical();
|
||||
if (!CollectionUtils.isEmpty(critical)) {
|
||||
builder.criticalParams(critical);
|
||||
}
|
||||
|
||||
String contentType = headers.getContentType();
|
||||
if (StringUtils.hasText(contentType)) {
|
||||
builder.contentType(contentType);
|
||||
}
|
||||
|
||||
String jwkSetUri = headers.getJwkSetUri();
|
||||
if (StringUtils.hasText(jwkSetUri)) {
|
||||
try {
|
||||
builder.jwkURL(new URI(jwkSetUri));
|
||||
} catch (Exception ex) {
|
||||
throw new JwtEncodingException(String.format(
|
||||
ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||
"Failed to convert '" + JoseHeaderNames.JKU + "' JOSE header"), ex);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> jwk = headers.getJwk();
|
||||
if (!CollectionUtils.isEmpty(jwk)) {
|
||||
try {
|
||||
builder.jwk(JWK.parse(jwk));
|
||||
} catch (Exception ex) {
|
||||
throw new JwtEncodingException(String.format(
|
||||
ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||
"Failed to convert '" + JoseHeaderNames.JWK + "' JOSE header"), ex);
|
||||
}
|
||||
}
|
||||
|
||||
String keyId = headers.getKeyId();
|
||||
if (StringUtils.hasText(keyId)) {
|
||||
builder.keyID(keyId);
|
||||
}
|
||||
|
||||
String type = headers.getType();
|
||||
if (StringUtils.hasText(type)) {
|
||||
builder.type(new JOSEObjectType(type));
|
||||
}
|
||||
|
||||
List<String> x509CertificateChain = headers.getX509CertificateChain();
|
||||
if (!CollectionUtils.isEmpty(x509CertificateChain)) {
|
||||
builder.x509CertChain(
|
||||
x509CertificateChain.stream()
|
||||
.map(Base64::new)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
String x509SHA1Thumbprint = headers.getX509SHA1Thumbprint();
|
||||
if (StringUtils.hasText(x509SHA1Thumbprint)) {
|
||||
builder.x509CertThumbprint(new Base64URL(x509SHA1Thumbprint));
|
||||
}
|
||||
|
||||
String x509SHA256Thumbprint = headers.getX509SHA256Thumbprint();
|
||||
if (StringUtils.hasText(x509SHA256Thumbprint)) {
|
||||
builder.x509CertSHA256Thumbprint(new Base64URL(x509SHA256Thumbprint));
|
||||
}
|
||||
|
||||
String x509Uri = headers.getX509Uri();
|
||||
if (StringUtils.hasText(x509Uri)) {
|
||||
try {
|
||||
builder.x509CertURL(new URI(x509Uri));
|
||||
} catch (Exception ex) {
|
||||
throw new JwtEncodingException(String.format(
|
||||
ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||
"Failed to convert '" + JoseHeaderNames.X5U + "' JOSE header"), ex);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> customHeaders = headers.getHeaders().entrySet().stream()
|
||||
.filter(header -> !JWSHeader.getRegisteredParameterNames().contains(header.getKey()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
if (!CollectionUtils.isEmpty(customHeaders)) {
|
||||
builder.customParams(customHeaders);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static class JwtClaimsSetConverter implements Converter<JwtClaimsSet, JWTClaimsSet> {
|
||||
|
||||
@Override
|
||||
public JWTClaimsSet convert(JwtClaimsSet claims) {
|
||||
JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
|
||||
|
||||
URL issuer = claims.getIssuer();
|
||||
if (issuer != null) {
|
||||
builder.issuer(issuer.toExternalForm());
|
||||
}
|
||||
|
||||
String subject = claims.getSubject();
|
||||
if (StringUtils.hasText(subject)) {
|
||||
builder.subject(subject);
|
||||
}
|
||||
|
||||
List<String> audience = claims.getAudience();
|
||||
if (!CollectionUtils.isEmpty(audience)) {
|
||||
builder.audience(audience);
|
||||
}
|
||||
|
||||
Instant issuedAt = claims.getIssuedAt();
|
||||
if (issuedAt != null) {
|
||||
builder.issueTime(Date.from(issuedAt));
|
||||
}
|
||||
|
||||
Instant expiresAt = claims.getExpiresAt();
|
||||
if (expiresAt != null) {
|
||||
builder.expirationTime(Date.from(expiresAt));
|
||||
}
|
||||
|
||||
Instant notBefore = claims.getNotBefore();
|
||||
if (notBefore != null) {
|
||||
builder.notBeforeTime(Date.from(notBefore));
|
||||
}
|
||||
|
||||
String jwtId = claims.getId();
|
||||
if (StringUtils.hasText(jwtId)) {
|
||||
builder.jwtID(jwtId);
|
||||
}
|
||||
|
||||
Map<String, Object> customClaims = claims.getClaims().entrySet().stream()
|
||||
.filter(claim -> !JWTClaimsSet.getRegisteredNames().contains(claim.getKey()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
if (!CollectionUtils.isEmpty(customClaims)) {
|
||||
customClaims.forEach(builder::claim);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -13,30 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.jose;
|
||||
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.util.Assert;
|
||||
package org.springframework.security.oauth2.jwt;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.springframework.security.oauth2.jose.JoseHeaderNames.ALG;
|
||||
import static org.springframework.security.oauth2.jose.JoseHeaderNames.CRIT;
|
||||
import static org.springframework.security.oauth2.jose.JoseHeaderNames.CTY;
|
||||
import static org.springframework.security.oauth2.jose.JoseHeaderNames.JKU;
|
||||
import static org.springframework.security.oauth2.jose.JoseHeaderNames.JWK;
|
||||
import static org.springframework.security.oauth2.jose.JoseHeaderNames.KID;
|
||||
import static org.springframework.security.oauth2.jose.JoseHeaderNames.TYP;
|
||||
import static org.springframework.security.oauth2.jose.JoseHeaderNames.X5C;
|
||||
import static org.springframework.security.oauth2.jose.JoseHeaderNames.X5T;
|
||||
import static org.springframework.security.oauth2.jose.JoseHeaderNames.X5T_S256;
|
||||
import static org.springframework.security.oauth2.jose.JoseHeaderNames.X5U;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
|
||||
import org.springframework.security.oauth2.jose.JwaAlgorithm;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The JOSE header is a JSON object representing the header parameters of a JSON Web Token,
|
||||
@@ -47,24 +36,25 @@ import static org.springframework.security.oauth2.jose.JoseHeaderNames.X5U;
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
* @see Jwt
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519#section-5">JWT JOSE Header</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515#section-4">JWS JOSE Header</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7516#section-4">JWE JOSE Header</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7519#section-5">JWT JOSE Header</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7515#section-4">JWS JOSE Header</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7516#section-4">JWE JOSE Header</a>
|
||||
*/
|
||||
public final class JoseHeader {
|
||||
private final Map<String, Object> headers;
|
||||
|
||||
private JoseHeader(Map<String, Object> headers) {
|
||||
this.headers = Collections.unmodifiableMap(new LinkedHashMap<>(headers));
|
||||
this.headers = Collections.unmodifiableMap(new HashMap<>(headers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JWS algorithm used to digitally sign the JWS.
|
||||
* Returns the {@link JwaAlgorithm JWA algorithm} used to digitally sign the JWS or encrypt the JWE.
|
||||
*
|
||||
* @return the JWS algorithm
|
||||
* @return the {@link JwaAlgorithm}
|
||||
*/
|
||||
public JwsAlgorithm getJwsAlgorithm() {
|
||||
return getHeader(ALG);
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends JwaAlgorithm> T getAlgorithm() {
|
||||
return (T) getHeader(JoseHeaderNames.ALG);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,8 +63,8 @@ public final class JoseHeader {
|
||||
*
|
||||
* @return the JWK Set URL
|
||||
*/
|
||||
public String getJwkSetUri() {
|
||||
return getHeader(JKU);
|
||||
public URL getJwkSetUrl() {
|
||||
return getHeader(JoseHeaderNames.JKU);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +74,7 @@ public final class JoseHeader {
|
||||
* @return the JSON Web Key
|
||||
*/
|
||||
public Map<String, Object> getJwk() {
|
||||
return getHeader(JWK);
|
||||
return getHeader(JoseHeaderNames.JWK);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +83,7 @@ public final class JoseHeader {
|
||||
* @return the key ID
|
||||
*/
|
||||
public String getKeyId() {
|
||||
return getHeader(KID);
|
||||
return getHeader(JoseHeaderNames.KID);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,18 +92,21 @@ public final class JoseHeader {
|
||||
*
|
||||
* @return the X.509 URL
|
||||
*/
|
||||
public String getX509Uri() {
|
||||
return getHeader(X5U);
|
||||
public URL getX509Url() {
|
||||
return getHeader(JoseHeaderNames.X5U);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the X.509 certificate chain that contains the X.509 public key certificate
|
||||
* or certificate chain corresponding to the key used to digitally sign the JWS or encrypt the JWE.
|
||||
* or certificate chain corresponding to the key used to digitally sign the JWS or
|
||||
* encrypt the JWE. The certificate or certificate chain is represented as a
|
||||
* {@code List} of certificate value {@code String}s. Each {@code String} in the
|
||||
* {@code List} is a Base64-encoded DER PKIX certificate value.
|
||||
*
|
||||
* @return the X.509 certificate chain
|
||||
*/
|
||||
public List<String> getX509CertificateChain() {
|
||||
return getHeader(X5C);
|
||||
return getHeader(JoseHeaderNames.X5C);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,7 +116,7 @@ public final class JoseHeader {
|
||||
* @return the X.509 certificate SHA-1 thumbprint
|
||||
*/
|
||||
public String getX509SHA1Thumbprint() {
|
||||
return getHeader(X5T);
|
||||
return getHeader(JoseHeaderNames.X5T);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,7 +126,25 @@ public final class JoseHeader {
|
||||
* @return the X.509 certificate SHA-256 thumbprint
|
||||
*/
|
||||
public String getX509SHA256Thumbprint() {
|
||||
return getHeader(X5T_S256);
|
||||
return getHeader(JoseHeaderNames.X5T_S256);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type header that declares the media type of the JWS/JWE.
|
||||
*
|
||||
* @return the type header
|
||||
*/
|
||||
public String getType() {
|
||||
return getHeader(JoseHeaderNames.TYP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content type header that declares the media type of the secured content (the payload).
|
||||
*
|
||||
* @return the content type header
|
||||
*/
|
||||
public String getContentType() {
|
||||
return getHeader(JoseHeaderNames.CTY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,25 +154,7 @@ public final class JoseHeader {
|
||||
* @return the critical headers
|
||||
*/
|
||||
public Set<String> getCritical() {
|
||||
return getHeader(CRIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type header that declares the media type of the JWS/JWE.
|
||||
*
|
||||
* @return the type header
|
||||
*/
|
||||
public String getType() {
|
||||
return getHeader(TYP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content type header that declares the media type of the secured content (the payload).
|
||||
*
|
||||
* @return the content type header
|
||||
*/
|
||||
public String getContentType() {
|
||||
return getHeader(CTY);
|
||||
return getHeader(JoseHeaderNames.CRIT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,13 +180,22 @@ public final class JoseHeader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link Builder}, initialized with the provided {@link JwsAlgorithm}.
|
||||
* Returns a new {@link Builder}.
|
||||
*
|
||||
* @param jwsAlgorithm the {@link JwsAlgorithm}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder withAlgorithm(JwsAlgorithm jwsAlgorithm) {
|
||||
return new Builder(jwsAlgorithm);
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link Builder}, initialized with the provided {@link JwaAlgorithm}.
|
||||
*
|
||||
* @param jwaAlgorithm the {@link JwaAlgorithm}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder withAlgorithm(JwaAlgorithm jwaAlgorithm) {
|
||||
return new Builder(jwaAlgorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,12 +211,14 @@ public final class JoseHeader {
|
||||
/**
|
||||
* A builder for {@link JoseHeader}.
|
||||
*/
|
||||
public static class Builder {
|
||||
private final Map<String, Object> headers = new LinkedHashMap<>();
|
||||
public static final class Builder {
|
||||
private final Map<String, Object> headers = new HashMap<>();
|
||||
|
||||
private Builder(JwsAlgorithm jwsAlgorithm) {
|
||||
Assert.notNull(jwsAlgorithm, "jwsAlgorithm cannot be null");
|
||||
header(ALG, jwsAlgorithm);
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
private Builder(JwaAlgorithm jwaAlgorithm) {
|
||||
algorithm(jwaAlgorithm);
|
||||
}
|
||||
|
||||
private Builder(JoseHeader headers) {
|
||||
@@ -222,15 +226,26 @@ public final class JoseHeader {
|
||||
this.headers.putAll(headers.getHeaders());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link JwaAlgorithm JWA algorithm} used to digitally sign the JWS or encrypt the JWE.
|
||||
*
|
||||
* @param jwaAlgorithm the {@link JwaAlgorithm}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder algorithm(JwaAlgorithm jwaAlgorithm) {
|
||||
Assert.notNull(jwaAlgorithm, "jwaAlgorithm cannot be null");
|
||||
return header(JoseHeaderNames.ALG, jwaAlgorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the JWK Set URL that refers to the resource of a set of JSON-encoded public keys,
|
||||
* one of which corresponds to the key used to digitally sign the JWS or encrypt the JWE.
|
||||
*
|
||||
* @param jwkSetUri the JWK Set URL
|
||||
* @param jwkSetUrl the JWK Set URL
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder jwkSetUri(String jwkSetUri) {
|
||||
return header(JKU, jwkSetUri);
|
||||
public Builder jwkSetUrl(String jwkSetUrl) {
|
||||
return header(JoseHeaderNames.JKU, convertAsURL(JoseHeaderNames.JKU, jwkSetUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,7 +256,7 @@ public final class JoseHeader {
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder jwk(Map<String, Object> jwk) {
|
||||
return header(JWK, jwk);
|
||||
return header(JoseHeaderNames.JWK, jwk);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,29 +266,32 @@ public final class JoseHeader {
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder keyId(String keyId) {
|
||||
return header(KID, keyId);
|
||||
return header(JoseHeaderNames.KID, keyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the X.509 URL that refers to the resource for the X.509 public key certificate
|
||||
* or certificate chain corresponding to the key used to digitally sign the JWS or encrypt the JWE.
|
||||
*
|
||||
* @param x509Uri the X.509 URL
|
||||
* @param x509Url the X.509 URL
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder x509Uri(String x509Uri) {
|
||||
return header(X5U, x509Uri);
|
||||
public Builder x509Url(String x509Url) {
|
||||
return header(JoseHeaderNames.X5U, convertAsURL(JoseHeaderNames.X5U, x509Url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the X.509 certificate chain that contains the X.509 public key certificate
|
||||
* or certificate chain corresponding to the key used to digitally sign the JWS or encrypt the JWE.
|
||||
* or certificate chain corresponding to the key used to digitally sign the JWS or
|
||||
* encrypt the JWE. The certificate or certificate chain is represented as a
|
||||
* {@code List} of certificate value {@code String}s. Each {@code String} in the
|
||||
* {@code List} is a Base64-encoded DER PKIX certificate value.
|
||||
*
|
||||
* @param x509CertificateChain the X.509 certificate chain
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder x509CertificateChain(List<String> x509CertificateChain) {
|
||||
return header(X5C, x509CertificateChain);
|
||||
return header(JoseHeaderNames.X5C, x509CertificateChain);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -284,7 +302,7 @@ public final class JoseHeader {
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder x509SHA1Thumbprint(String x509SHA1Thumbprint) {
|
||||
return header(X5T, x509SHA1Thumbprint);
|
||||
return header(JoseHeaderNames.X5T, x509SHA1Thumbprint);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,7 +313,27 @@ public final class JoseHeader {
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder x509SHA256Thumbprint(String x509SHA256Thumbprint) {
|
||||
return header(X5T_S256, x509SHA256Thumbprint);
|
||||
return header(JoseHeaderNames.X5T_S256, x509SHA256Thumbprint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type header that declares the media type of the JWS/JWE.
|
||||
*
|
||||
* @param type the type header
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder type(String type) {
|
||||
return header(JoseHeaderNames.TYP, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content type header that declares the media type of the secured content (the payload).
|
||||
*
|
||||
* @param contentType the content type header
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder contentType(String contentType) {
|
||||
return header(JoseHeaderNames.CTY, contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -306,27 +344,7 @@ public final class JoseHeader {
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder critical(Set<String> headerNames) {
|
||||
return header(CRIT, headerNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type header that declares the media type of the JWS/JWE.
|
||||
*
|
||||
* @param type the type header
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder type(String type) {
|
||||
return header(TYP, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content type header that declares the media type of the secured content (the payload).
|
||||
*
|
||||
* @param contentType the content type header
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder contentType(String contentType) {
|
||||
return header(CTY, contentType);
|
||||
return header(JoseHeaderNames.CRIT, headerNames);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -364,5 +382,13 @@ public final class JoseHeader {
|
||||
Assert.notEmpty(this.headers, "headers cannot be empty");
|
||||
return new JoseHeader(this.headers);
|
||||
}
|
||||
|
||||
private static URL convertAsURL(String header, String value) {
|
||||
URL convertedValue = ClaimConversionService.getSharedInstance().convert(value, URL.class);
|
||||
Assert.isTrue(convertedValue != null,
|
||||
() -> "Unable to convert header '" + header + "' of type '" + value.getClass() + "' to URL.");
|
||||
return convertedValue;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.jose;
|
||||
package org.springframework.security.oauth2.jwt;
|
||||
|
||||
/**
|
||||
* The Registered Header Parameter Names defined by the JSON Web Token (JWT),
|
||||
@@ -28,69 +28,72 @@ package org.springframework.security.oauth2.jose;
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515#section-4">JWS JOSE Header</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7516#section-4">JWE JOSE Header</a>
|
||||
*/
|
||||
public interface JoseHeaderNames {
|
||||
public final class JoseHeaderNames {
|
||||
|
||||
/**
|
||||
* {@code alg} - the algorithm header identifies the cryptographic algorithm used to secure a JWS or JWE
|
||||
*/
|
||||
String ALG = "alg";
|
||||
public static final String ALG = "alg";
|
||||
|
||||
/**
|
||||
* {@code jku} - the JWK Set URL header is a URI that refers to a resource for a set of JSON-encoded public keys,
|
||||
* one of which corresponds to the key used to digitally sign a JWS or encrypt a JWE
|
||||
*/
|
||||
String JKU = "jku";
|
||||
public static final String JKU = "jku";
|
||||
|
||||
/**
|
||||
* {@code jwk} - the JSON Web Key header is the public key that corresponds to the key
|
||||
* used to digitally sign a JWS or encrypt a JWE
|
||||
*/
|
||||
String JWK = "jwk";
|
||||
public static final String JWK = "jwk";
|
||||
|
||||
/**
|
||||
* {@code kid} - the key ID header is a hint indicating which key was used to secure a JWS or JWE
|
||||
*/
|
||||
String KID = "kid";
|
||||
public static final String KID = "kid";
|
||||
|
||||
/**
|
||||
* {@code x5u} - the X.509 URL header is a URI that refers to a resource for the X.509 public key certificate
|
||||
* or certificate chain corresponding to the key used to digitally sign a JWS or encrypt a JWE
|
||||
*/
|
||||
String X5U = "x5u";
|
||||
public static final String X5U = "x5u";
|
||||
|
||||
/**
|
||||
* {@code x5c} - the X.509 certificate chain header contains the X.509 public key certificate
|
||||
* or certificate chain corresponding to the key used to digitally sign a JWS or encrypt a JWE
|
||||
*/
|
||||
String X5C = "x5c";
|
||||
public static final String X5C = "x5c";
|
||||
|
||||
/**
|
||||
* {@code x5t} - the X.509 certificate SHA-1 thumbprint header is a base64url-encoded SHA-1 thumbprint (a.k.a. digest)
|
||||
* of the DER encoding of the X.509 certificate corresponding to the key used to digitally sign a JWS or encrypt a JWE
|
||||
*/
|
||||
String X5T = "x5t";
|
||||
public static final String X5T = "x5t";
|
||||
|
||||
/**
|
||||
* {@code x5t#S256} - the X.509 certificate SHA-256 thumbprint header is a base64url-encoded SHA-256 thumbprint (a.k.a. digest)
|
||||
* of the DER encoding of the X.509 certificate corresponding to the key used to digitally sign a JWS or encrypt a JWE
|
||||
*/
|
||||
String X5T_S256 = "x5t#S256";
|
||||
public static final String X5T_S256 = "x5t#S256";
|
||||
|
||||
/**
|
||||
* {@code typ} - the type header is used by JWS/JWE applications to declare the media type of a JWS/JWE
|
||||
*/
|
||||
String TYP = "typ";
|
||||
public static final String TYP = "typ";
|
||||
|
||||
/**
|
||||
* {@code cty} - the content type header is used by JWS/JWE applications to declare the media type
|
||||
* of the secured content (the payload)
|
||||
*/
|
||||
String CTY = "cty";
|
||||
public static final String CTY = "cty";
|
||||
|
||||
/**
|
||||
* {@code crit} - the critical header indicates that extensions to the JWS/JWE/JWA specifications
|
||||
* are being used that MUST be understood and processed
|
||||
*/
|
||||
String CRIT = "crit";
|
||||
public static final String CRIT = "crit";
|
||||
|
||||
private JoseHeaderNames() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -15,23 +15,16 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.jwt;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.AUD;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.EXP;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.IAT;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.ISS;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.JTI;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.NBF;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.SUB;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The {@link Jwt JWT} Claims Set is a JSON object representing the claims conveyed by a JSON Web Token.
|
||||
@@ -41,13 +34,13 @@ import static org.springframework.security.oauth2.jwt.JwtClaimNames.SUB;
|
||||
* @since 0.0.1
|
||||
* @see Jwt
|
||||
* @see JwtClaimAccessor
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519#section-4">JWT Claims Set</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7519#section-4">JWT Claims Set</a>
|
||||
*/
|
||||
public final class JwtClaimsSet implements JwtClaimAccessor {
|
||||
private final Map<String, Object> claims;
|
||||
|
||||
private JwtClaimsSet(Map<String, Object> claims) {
|
||||
this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
|
||||
this.claims = Collections.unmodifiableMap(new HashMap<>(claims));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,7 +53,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
|
||||
*
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder withClaims() {
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@@ -77,8 +70,8 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
|
||||
/**
|
||||
* A builder for {@link JwtClaimsSet}.
|
||||
*/
|
||||
public static class Builder {
|
||||
private final Map<String, Object> claims = new LinkedHashMap<>();
|
||||
public static final class Builder {
|
||||
private final Map<String, Object> claims = new HashMap<>();
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
@@ -94,8 +87,8 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
|
||||
* @param issuer the issuer identifier
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder issuer(URL issuer) {
|
||||
return claim(ISS, issuer);
|
||||
public Builder issuer(String issuer) {
|
||||
return claim(JwtClaimNames.ISS, issuer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +98,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder subject(String subject) {
|
||||
return claim(SUB, subject);
|
||||
return claim(JwtClaimNames.SUB, subject);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,7 +108,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder audience(List<String> audience) {
|
||||
return claim(AUD, audience);
|
||||
return claim(JwtClaimNames.AUD, audience);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,7 +119,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder expiresAt(Instant expiresAt) {
|
||||
return claim(EXP, expiresAt);
|
||||
return claim(JwtClaimNames.EXP, expiresAt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,7 +130,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder notBefore(Instant notBefore) {
|
||||
return claim(NBF, notBefore);
|
||||
return claim(JwtClaimNames.NBF, notBefore);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,7 +140,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder issuedAt(Instant issuedAt) {
|
||||
return claim(IAT, issuedAt);
|
||||
return claim(JwtClaimNames.IAT, issuedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,7 +150,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder id(String jti) {
|
||||
return claim(JTI, jti);
|
||||
return claim(JwtClaimNames.JTI, jti);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,10 +168,10 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} to be provided access to the claims set
|
||||
* A {@code Consumer} to be provided access to the claims
|
||||
* allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param claimsConsumer a {@code Consumer} of the claims set
|
||||
* @param claimsConsumer a {@code Consumer} of the claims
|
||||
*/
|
||||
public Builder claims(Consumer<Map<String, Object>> claimsConsumer) {
|
||||
claimsConsumer.accept(this.claims);
|
||||
@@ -192,6 +185,17 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
|
||||
*/
|
||||
public JwtClaimsSet build() {
|
||||
Assert.notEmpty(this.claims, "claims cannot be empty");
|
||||
|
||||
// The value of the 'iss' claim is a String or URL (StringOrURI).
|
||||
// Attempt to convert to URL.
|
||||
Object issuer = this.claims.get(JwtClaimNames.ISS);
|
||||
if (issuer != null) {
|
||||
URL convertedValue = ClaimConversionService.getSharedInstance().convert(issuer, URL.class);
|
||||
if (convertedValue != null) {
|
||||
this.claims.put(JwtClaimNames.ISS, convertedValue);
|
||||
}
|
||||
}
|
||||
|
||||
return new JwtClaimsSet(this.claims);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.jwt;
|
||||
|
||||
import org.springframework.security.oauth2.jose.JoseHeader;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are responsible for encoding
|
||||
* a JSON Web Token (JWT) to it's compact claims representation format.
|
||||
@@ -34,11 +32,11 @@ import org.springframework.security.oauth2.jose.JoseHeader;
|
||||
* @see JoseHeader
|
||||
* @see JwtClaimsSet
|
||||
* @see JwtDecoder
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token (JWT)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515">JSON Web Signature (JWS)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7516">JSON Web Encryption (JWE)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515#section-3.1">JWS Compact Serialization</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7516#section-3.1">JWE Compact Serialization</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7519">JSON Web Token (JWT)</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7515">JSON Web Signature (JWS)</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7516">JSON Web Encryption (JWE)</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7515#section-3.1">JWS Compact Serialization</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7516#section-3.1">JWE Compact Serialization</a>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface JwtEncoder {
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
* 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.jwt;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.nimbusds.jose.JOSEException;
|
||||
import com.nimbusds.jose.JOSEObjectType;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
import com.nimbusds.jose.JWSSigner;
|
||||
import com.nimbusds.jose.crypto.factories.DefaultJWSSignerFactory;
|
||||
import com.nimbusds.jose.jwk.JWK;
|
||||
import com.nimbusds.jose.jwk.JWKMatcher;
|
||||
import com.nimbusds.jose.jwk.JWKSelector;
|
||||
import com.nimbusds.jose.jwk.KeyType;
|
||||
import com.nimbusds.jose.jwk.KeyUse;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import com.nimbusds.jose.produce.JWSSignerFactory;
|
||||
import com.nimbusds.jose.util.Base64;
|
||||
import com.nimbusds.jose.util.Base64URL;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link JwtEncoder} that encodes a JSON Web Token (JWT) using the
|
||||
* JSON Web Signature (JWS) Compact Serialization format. The private/secret key used for
|
||||
* signing the JWS is supplied by the {@code com.nimbusds.jose.jwk.source.JWKSource}
|
||||
* provided via the constructor.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This implementation uses the Nimbus JOSE + JWT SDK.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
* @see JwtEncoder
|
||||
* @see com.nimbusds.jose.jwk.source.JWKSource
|
||||
* @see com.nimbusds.jose.jwk.JWK
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7519">JSON Web Token (JWT)</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7515">JSON Web Signature (JWS)</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7515#section-3.1">JWS Compact Serialization</a>
|
||||
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-jose-jwt">Nimbus JOSE + JWT SDK</a>
|
||||
*/
|
||||
public final class NimbusJwsEncoder implements JwtEncoder {
|
||||
private static final String ENCODING_ERROR_MESSAGE_TEMPLATE = "An error occurred while attempting to encode the Jwt: %s";
|
||||
private static final Converter<JoseHeader, JWSHeader> JWS_HEADER_CONVERTER = new JwsHeaderConverter();
|
||||
private static final Converter<JwtClaimsSet, JWTClaimsSet> JWT_CLAIMS_SET_CONVERTER = new JwtClaimsSetConverter();
|
||||
private static final JWSSignerFactory JWS_SIGNER_FACTORY = new DefaultJWSSignerFactory();
|
||||
private final Map<JWK, JWSSigner> jwsSigners = new ConcurrentHashMap<>();
|
||||
private final JWKSource<SecurityContext> jwkSource;
|
||||
|
||||
/**
|
||||
* Constructs a {@code NimbusJwsEncoder} using the provided parameters.
|
||||
* @param jwkSource the {@code com.nimbusds.jose.jwk.source.JWKSource}
|
||||
*/
|
||||
public NimbusJwsEncoder(JWKSource<SecurityContext> jwkSource) {
|
||||
Assert.notNull(jwkSource, "jwkSource cannot be null");
|
||||
this.jwkSource = jwkSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Jwt encode(JoseHeader headers, JwtClaimsSet claims) throws JwtEncodingException {
|
||||
Assert.notNull(headers, "headers cannot be null");
|
||||
Assert.notNull(claims, "claims cannot be null");
|
||||
|
||||
JWK jwk = selectJwk(headers);
|
||||
headers = addKeyIdentifierHeadersIfNecessary(headers, jwk);
|
||||
|
||||
String jws = serialize(headers, claims, jwk);
|
||||
|
||||
return new Jwt(jws, claims.getIssuedAt(), claims.getExpiresAt(), headers.getHeaders(), claims.getClaims());
|
||||
}
|
||||
|
||||
private JWK selectJwk(JoseHeader headers) {
|
||||
List<JWK> jwks;
|
||||
try {
|
||||
JWKSelector jwkSelector = new JWKSelector(createJwkMatcher(headers));
|
||||
jwks = this.jwkSource.get(jwkSelector, null);
|
||||
} catch (Exception ex) {
|
||||
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||
"Failed to select a JWK signing key -> " + ex.getMessage()), ex);
|
||||
}
|
||||
|
||||
if (jwks.size() > 1) {
|
||||
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||
"Found multiple JWK signing keys for algorithm '" + headers.getAlgorithm().getName() + "'"));
|
||||
}
|
||||
|
||||
if (jwks.isEmpty()) {
|
||||
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||
"Failed to select a JWK signing key"));
|
||||
}
|
||||
|
||||
return jwks.get(0);
|
||||
}
|
||||
|
||||
private String serialize(JoseHeader headers, JwtClaimsSet claims, JWK jwk) {
|
||||
JWSHeader jwsHeader = JWS_HEADER_CONVERTER.convert(headers);
|
||||
JWTClaimsSet jwtClaimsSet = JWT_CLAIMS_SET_CONVERTER.convert(claims);
|
||||
|
||||
JWSSigner jwsSigner = this.jwsSigners.computeIfAbsent(jwk, NimbusJwsEncoder::createSigner);
|
||||
|
||||
SignedJWT signedJwt = new SignedJWT(jwsHeader, jwtClaimsSet);
|
||||
try {
|
||||
signedJwt.sign(jwsSigner);
|
||||
} catch (JOSEException ex) {
|
||||
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||
"Failed to sign the JWT -> " + ex.getMessage()), ex);
|
||||
}
|
||||
return signedJwt.serialize();
|
||||
}
|
||||
|
||||
private static JWKMatcher createJwkMatcher(JoseHeader headers) {
|
||||
JWSAlgorithm jwsAlgorithm = JWSAlgorithm.parse(headers.getAlgorithm().getName());
|
||||
|
||||
if (JWSAlgorithm.Family.RSA.contains(jwsAlgorithm) || JWSAlgorithm.Family.EC.contains(jwsAlgorithm)) {
|
||||
// @formatter:off
|
||||
return new JWKMatcher.Builder()
|
||||
.keyType(KeyType.forAlgorithm(jwsAlgorithm))
|
||||
.keyID(headers.getKeyId())
|
||||
.keyUses(KeyUse.SIGNATURE, null)
|
||||
.algorithms(jwsAlgorithm, null)
|
||||
.x509CertSHA256Thumbprint(Base64URL.from(headers.getX509SHA256Thumbprint()))
|
||||
.build();
|
||||
// @formatter:on
|
||||
} else if (JWSAlgorithm.Family.HMAC_SHA.contains(jwsAlgorithm)) {
|
||||
// @formatter:off
|
||||
return new JWKMatcher.Builder()
|
||||
.keyType(KeyType.forAlgorithm(jwsAlgorithm))
|
||||
.keyID(headers.getKeyId())
|
||||
.privateOnly(true)
|
||||
.algorithms(jwsAlgorithm, null)
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static JoseHeader addKeyIdentifierHeadersIfNecessary(JoseHeader headers, JWK jwk) {
|
||||
// Check if headers have already been added
|
||||
if (StringUtils.hasText(headers.getKeyId()) && StringUtils.hasText(headers.getX509SHA256Thumbprint())) {
|
||||
return headers;
|
||||
}
|
||||
// Check if headers can be added from JWK
|
||||
if (!StringUtils.hasText(jwk.getKeyID()) && jwk.getX509CertSHA256Thumbprint() == null) {
|
||||
return headers;
|
||||
}
|
||||
|
||||
JoseHeader.Builder headersBuilder = JoseHeader.from(headers);
|
||||
if (!StringUtils.hasText(headers.getKeyId()) && StringUtils.hasText(jwk.getKeyID())) {
|
||||
headersBuilder.keyId(jwk.getKeyID());
|
||||
}
|
||||
if (!StringUtils.hasText(headers.getX509SHA256Thumbprint()) && jwk.getX509CertSHA256Thumbprint() != null) {
|
||||
headersBuilder.x509SHA256Thumbprint(jwk.getX509CertSHA256Thumbprint().toString());
|
||||
}
|
||||
|
||||
return headersBuilder.build();
|
||||
}
|
||||
|
||||
private static JWSSigner createSigner(JWK jwk) {
|
||||
try {
|
||||
return JWS_SIGNER_FACTORY.createJWSSigner(jwk);
|
||||
} catch (JOSEException ex) {
|
||||
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||
"Failed to create a JWS Signer -> " + ex.getMessage()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static class JwsHeaderConverter implements Converter<JoseHeader, JWSHeader> {
|
||||
|
||||
@Override
|
||||
public JWSHeader convert(JoseHeader headers) {
|
||||
JWSHeader.Builder builder = new JWSHeader.Builder(JWSAlgorithm.parse(headers.getAlgorithm().getName()));
|
||||
|
||||
if (headers.getJwkSetUrl() != null) {
|
||||
builder.jwkURL(convertAsURI(JoseHeaderNames.JKU, headers.getJwkSetUrl()));
|
||||
}
|
||||
|
||||
Map<String, Object> jwk = headers.getJwk();
|
||||
if (!CollectionUtils.isEmpty(jwk)) {
|
||||
try {
|
||||
builder.jwk(JWK.parse(jwk));
|
||||
} catch (Exception ex) {
|
||||
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||
"Unable to convert '" + JoseHeaderNames.JWK + "' JOSE header"), ex);
|
||||
}
|
||||
}
|
||||
|
||||
String keyId = headers.getKeyId();
|
||||
if (StringUtils.hasText(keyId)) {
|
||||
builder.keyID(keyId);
|
||||
}
|
||||
|
||||
if (headers.getX509Url() != null) {
|
||||
builder.x509CertURL(convertAsURI(JoseHeaderNames.X5U, headers.getX509Url()));
|
||||
}
|
||||
|
||||
List<String> x509CertificateChain = headers.getX509CertificateChain();
|
||||
if (!CollectionUtils.isEmpty(x509CertificateChain)) {
|
||||
List<Base64> x5cList = new ArrayList<>();
|
||||
x509CertificateChain.forEach((x5c) -> x5cList.add(new Base64(x5c)));
|
||||
if (!x5cList.isEmpty()) {
|
||||
builder.x509CertChain(x5cList);
|
||||
}
|
||||
}
|
||||
|
||||
String x509SHA1Thumbprint = headers.getX509SHA1Thumbprint();
|
||||
if (StringUtils.hasText(x509SHA1Thumbprint)) {
|
||||
builder.x509CertThumbprint(new Base64URL(x509SHA1Thumbprint));
|
||||
}
|
||||
|
||||
String x509SHA256Thumbprint = headers.getX509SHA256Thumbprint();
|
||||
if (StringUtils.hasText(x509SHA256Thumbprint)) {
|
||||
builder.x509CertSHA256Thumbprint(new Base64URL(x509SHA256Thumbprint));
|
||||
}
|
||||
|
||||
String type = headers.getType();
|
||||
if (StringUtils.hasText(type)) {
|
||||
builder.type(new JOSEObjectType(type));
|
||||
}
|
||||
|
||||
String contentType = headers.getContentType();
|
||||
if (StringUtils.hasText(contentType)) {
|
||||
builder.contentType(contentType);
|
||||
}
|
||||
|
||||
Set<String> critical = headers.getCritical();
|
||||
if (!CollectionUtils.isEmpty(critical)) {
|
||||
builder.criticalParams(critical);
|
||||
}
|
||||
|
||||
Map<String, Object> customHeaders = new HashMap<>();
|
||||
headers.getHeaders().forEach((name, value) -> {
|
||||
if (!JWSHeader.getRegisteredParameterNames().contains(name)) {
|
||||
customHeaders.put(name, value);
|
||||
}
|
||||
});
|
||||
if (!customHeaders.isEmpty()) {
|
||||
builder.customParams(customHeaders);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static URI convertAsURI(String header, URL url) {
|
||||
try {
|
||||
return url.toURI();
|
||||
} catch (Exception ex) {
|
||||
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||
"Unable to convert '" + header + "' JOSE header to a URI"), ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class JwtClaimsSetConverter implements Converter<JwtClaimsSet, JWTClaimsSet> {
|
||||
|
||||
@Override
|
||||
public JWTClaimsSet convert(JwtClaimsSet claims) {
|
||||
JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
|
||||
|
||||
// NOTE: The value of the 'iss' claim is a String or URL (StringOrURI).
|
||||
Object issuer = claims.getClaim(JwtClaimNames.ISS);
|
||||
if (issuer != null) {
|
||||
builder.issuer(issuer.toString());
|
||||
}
|
||||
|
||||
String subject = claims.getSubject();
|
||||
if (StringUtils.hasText(subject)) {
|
||||
builder.subject(subject);
|
||||
}
|
||||
|
||||
List<String> audience = claims.getAudience();
|
||||
if (!CollectionUtils.isEmpty(audience)) {
|
||||
builder.audience(audience);
|
||||
}
|
||||
|
||||
Instant expiresAt = claims.getExpiresAt();
|
||||
if (expiresAt != null) {
|
||||
builder.expirationTime(Date.from(expiresAt));
|
||||
}
|
||||
|
||||
Instant notBefore = claims.getNotBefore();
|
||||
if (notBefore != null) {
|
||||
builder.notBeforeTime(Date.from(notBefore));
|
||||
}
|
||||
|
||||
Instant issuedAt = claims.getIssuedAt();
|
||||
if (issuedAt != null) {
|
||||
builder.issueTime(Date.from(issuedAt));
|
||||
}
|
||||
|
||||
String jwtId = claims.getId();
|
||||
if (StringUtils.hasText(jwtId)) {
|
||||
builder.jwtID(jwtId);
|
||||
}
|
||||
|
||||
Map<String, Object> customClaims = new HashMap<>();
|
||||
claims.getClaims().forEach((name, value) -> {
|
||||
if (!JWTClaimsSet.getRegisteredNames().contains(name)) {
|
||||
customClaims.put(name, value);
|
||||
}
|
||||
});
|
||||
if (!customClaims.isEmpty()) {
|
||||
customClaims.forEach(builder::claim);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.server.authorization;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2AuthorizationConsentService} that stores {@link OAuth2AuthorizationConsent}'s in-memory.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This implementation should ONLY be used during development/testing.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.2
|
||||
* @see OAuth2AuthorizationConsentService
|
||||
*/
|
||||
public final class InMemoryOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {
|
||||
private final Map<Integer, OAuth2AuthorizationConsent> authorizationConsents = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs an {@code InMemoryOAuth2AuthorizationConsentService}.
|
||||
*/
|
||||
public InMemoryOAuth2AuthorizationConsentService() {
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code InMemoryOAuth2AuthorizationConsentService} using the provided parameters.
|
||||
*
|
||||
* @param authorizationConsents the authorization consent(s)
|
||||
*/
|
||||
public InMemoryOAuth2AuthorizationConsentService(OAuth2AuthorizationConsent... authorizationConsents) {
|
||||
this(Arrays.asList(authorizationConsents));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code InMemoryOAuth2AuthorizationConsentService} using the provided parameters.
|
||||
*
|
||||
* @param authorizationConsents the authorization consent(s)
|
||||
*/
|
||||
public InMemoryOAuth2AuthorizationConsentService(List<OAuth2AuthorizationConsent> authorizationConsents) {
|
||||
Assert.notNull(authorizationConsents, "authorizationConsents cannot be null");
|
||||
authorizationConsents.forEach(authorizationConsent -> {
|
||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
||||
int id = getId(authorizationConsent);
|
||||
Assert.isTrue(!this.authorizationConsents.containsKey(id),
|
||||
"The authorizationConsent must be unique. Found duplicate, with registered client id: ["
|
||||
+ authorizationConsent.getRegisteredClientId()
|
||||
+ "] and principal name: [" + authorizationConsent.getPrincipalName() + "]");
|
||||
this.authorizationConsents.put(id, authorizationConsent);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
||||
int id = getId(authorizationConsent);
|
||||
this.authorizationConsents.put(id, authorizationConsent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
||||
int id = getId(authorizationConsent);
|
||||
this.authorizationConsents.remove(id, authorizationConsent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) {
|
||||
Assert.hasText(registeredClientId, "registeredClientId cannot be empty");
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
int id = getId(registeredClientId, principalName);
|
||||
return this.authorizationConsents.get(id);
|
||||
}
|
||||
|
||||
private static int getId(String registeredClientId, String principalName) {
|
||||
return Objects.hash(registeredClientId, principalName);
|
||||
}
|
||||
|
||||
private static int getId(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
return getId(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -15,89 +15,133 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.oauth2.server.authorization.Version;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2AuthorizationService} that stores {@link OAuth2Authorization}'s in-memory.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This implementation should ONLY be used during development/testing.
|
||||
*
|
||||
* @author Krisztian Toth
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
* @see OAuth2AuthorizationService
|
||||
*/
|
||||
public final class InMemoryOAuth2AuthorizationService implements OAuth2AuthorizationService {
|
||||
private final Map<OAuth2AuthorizationId, OAuth2Authorization> authorizations = new ConcurrentHashMap<>();
|
||||
private final Map<String, OAuth2Authorization> authorizations = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs an {@code InMemoryOAuth2AuthorizationService}.
|
||||
*/
|
||||
public InMemoryOAuth2AuthorizationService() {
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code InMemoryOAuth2AuthorizationService} using the provided parameters.
|
||||
*
|
||||
* @param authorizations the authorization(s)
|
||||
*/
|
||||
public InMemoryOAuth2AuthorizationService(OAuth2Authorization... authorizations) {
|
||||
this(Arrays.asList(authorizations));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code InMemoryOAuth2AuthorizationService} using the provided parameters.
|
||||
*
|
||||
* @param authorizations the authorization(s)
|
||||
*/
|
||||
public InMemoryOAuth2AuthorizationService(List<OAuth2Authorization> authorizations) {
|
||||
Assert.notNull(authorizations, "authorizations cannot be null");
|
||||
authorizations.forEach(authorization -> {
|
||||
Assert.notNull(authorization, "authorization cannot be null");
|
||||
Assert.isTrue(!this.authorizations.containsKey(authorization.getId()),
|
||||
"The authorization must be unique. Found duplicate identifier: " + authorization.getId());
|
||||
this.authorizations.put(authorization.getId(), authorization);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(OAuth2Authorization authorization) {
|
||||
Assert.notNull(authorization, "authorization cannot be null");
|
||||
OAuth2AuthorizationId authorizationId = new OAuth2AuthorizationId(
|
||||
authorization.getRegisteredClientId(), authorization.getPrincipalName());
|
||||
this.authorizations.put(authorizationId, authorization);
|
||||
this.authorizations.put(authorization.getId(), authorization);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(OAuth2Authorization authorization) {
|
||||
Assert.notNull(authorization, "authorization cannot be null");
|
||||
OAuth2AuthorizationId authorizationId = new OAuth2AuthorizationId(
|
||||
authorization.getRegisteredClientId(), authorization.getPrincipalName());
|
||||
this.authorizations.remove(authorizationId, authorization);
|
||||
this.authorizations.remove(authorization.getId(), authorization);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public OAuth2Authorization findByToken(String token, @Nullable TokenType tokenType) {
|
||||
Assert.hasText(token, "token cannot be empty");
|
||||
return this.authorizations.values().stream()
|
||||
.filter(authorization -> hasToken(authorization, token, tokenType))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
public OAuth2Authorization findById(String id) {
|
||||
Assert.hasText(id, "id cannot be empty");
|
||||
return this.authorizations.get(id);
|
||||
}
|
||||
|
||||
private boolean hasToken(OAuth2Authorization authorization, String token, TokenType tokenType) {
|
||||
if (OAuth2AuthorizationAttributeNames.STATE.equals(tokenType.getValue())) {
|
||||
return token.equals(authorization.getAttribute(OAuth2AuthorizationAttributeNames.STATE));
|
||||
} else if (TokenType.AUTHORIZATION_CODE.equals(tokenType)) {
|
||||
return token.equals(authorization.getAttribute(OAuth2AuthorizationAttributeNames.CODE));
|
||||
} else if (TokenType.ACCESS_TOKEN.equals(tokenType)) {
|
||||
return authorization.getAccessToken() != null &&
|
||||
authorization.getAccessToken().getTokenValue().equals(token);
|
||||
@Nullable
|
||||
@Override
|
||||
public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) {
|
||||
Assert.hasText(token, "token cannot be empty");
|
||||
for (OAuth2Authorization authorization : this.authorizations.values()) {
|
||||
if (hasToken(authorization, token, tokenType)) {
|
||||
return authorization;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean hasToken(OAuth2Authorization authorization, String token, @Nullable OAuth2TokenType tokenType) {
|
||||
if (tokenType == null) {
|
||||
return matchesState(authorization, token) ||
|
||||
matchesAuthorizationCode(authorization, token) ||
|
||||
matchesAccessToken(authorization, token) ||
|
||||
matchesRefreshToken(authorization, token);
|
||||
} else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
|
||||
return matchesState(authorization, token);
|
||||
} else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) {
|
||||
return matchesAuthorizationCode(authorization, token);
|
||||
} else if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenType)) {
|
||||
return matchesAccessToken(authorization, token);
|
||||
} else if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) {
|
||||
return matchesRefreshToken(authorization, token);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class OAuth2AuthorizationId implements Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
private final String registeredClientId;
|
||||
private final String principalName;
|
||||
private static boolean matchesState(OAuth2Authorization authorization, String token) {
|
||||
return token.equals(authorization.getAttribute(OAuth2ParameterNames.STATE));
|
||||
}
|
||||
|
||||
private OAuth2AuthorizationId(String registeredClientId, String principalName) {
|
||||
this.registeredClientId = registeredClientId;
|
||||
this.principalName = principalName;
|
||||
}
|
||||
private static boolean matchesAuthorizationCode(OAuth2Authorization authorization, String token) {
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode =
|
||||
authorization.getToken(OAuth2AuthorizationCode.class);
|
||||
return authorizationCode != null && authorizationCode.getToken().getTokenValue().equals(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
OAuth2AuthorizationId that = (OAuth2AuthorizationId) obj;
|
||||
return Objects.equals(this.registeredClientId, that.registeredClientId) &&
|
||||
Objects.equals(this.principalName, that.principalName);
|
||||
}
|
||||
private static boolean matchesAccessToken(OAuth2Authorization authorization, String token) {
|
||||
OAuth2Authorization.Token<OAuth2AccessToken> accessToken =
|
||||
authorization.getToken(OAuth2AccessToken.class);
|
||||
return accessToken != null && accessToken.getToken().getTokenValue().equals(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.registeredClientId, this.principalName);
|
||||
}
|
||||
private static boolean matchesRefreshToken(OAuth2Authorization authorization, String token) {
|
||||
OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken =
|
||||
authorization.getToken(OAuth2RefreshToken.class);
|
||||
return refreshToken != null && refreshToken.getToken().getTokenValue().equals(token);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user