diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..aaeb72f2a --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,49 @@ + + + + +### Summary + + + +### Actual Behavior + + + +### Expected Behavior + + + +### Configuration + + + +### Version + + + +### Sample + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..570bf5e02 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,16 @@ + + + + + diff --git a/.gitignore b/.gitignore index eaf2dd9f7..f68c4b90b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,22 @@ +*# +.#* +*~ +_site/ */src/META-INF/ */src/main/java/META-INF/ samples/*/*/src/main/webapp/META-INF/ +build/ target/ +bin/ .classpath .project .DS_Store .settings/ +.springBeans *.iml *.iws *.ipr .idea/ +./code/ cargo-installs/ atlassian-ide-plugin.xml diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 000000000..0e7dabeff --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1 @@ +-Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom \ No newline at end of file diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 000000000..3b8cf46e1 --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1 @@ +-DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local -P spring diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 000000000..5fd4d5023 Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..eb9194764 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.3/apache-maven-3.3.3-bin.zip \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index dff5f3a5d..2eeabb479 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,19 @@ language: java + +dist: trusty + +jdk: + - openjdk8 + - openjdk7 + +services: + - redis-server + +install: ./mvnw -U install --quiet -DskipTests=true -P bootstrap + +script: + - jdk_switcher use openjdk7 + - ./mvnw clean test -P bootstrap + - jdk_switcher use openjdk8 + - ./mvnw -U clean checkstyle:check -P spring5 + - ./mvnw -f spring-security-oauth2 -U clean test -P spring5 diff --git a/CODE_OF_CONDUCT.adoc b/CODE_OF_CONDUCT.adoc new file mode 100644 index 000000000..17783c7c0 --- /dev/null +++ b/CODE_OF_CONDUCT.adoc @@ -0,0 +1,44 @@ += Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of fostering an open +and welcoming community, we pledge to respect all people who contribute through reporting +issues, posting feature requests, updating documentation, submitting pull requests or +patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for +everyone, regardless of level of experience, gender, gender identity and expression, +sexual orientation, disability, personal appearance, body size, race, ethnicity, age, +religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic addresses, + without explicit permission +* Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or reject comments, +commits, code, wiki edits, issues, and other contributions that are not aligned to this +Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors +that they deem inappropriate, threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to fairly and +consistently applying these principles to every aspect of managing this project. Project +maintainers who do not follow or enforce the Code of Conduct may be permanently removed +from the project team. + +This Code of Conduct applies both within project spaces and in public spaces when an +individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by +contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will +be reviewed and investigated and will result in a response that is deemed necessary and +appropriate to the circumstances. Maintainers are obligated to maintain confidentiality +with regard to the reporter of an incident. + +This Code of Conduct is adapted from the +https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at +https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/] diff --git a/README.md b/README.md index 16b62e347..64beaafd3 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,35 @@ +# spring-security-oauth is no longer actively maintained by VMware, Inc. + +## This project has been replaced by the OAuth2 support provided by [Spring Security](https://spring.io/projects/spring-security) (client and resource server) and [Spring Authorization Server](https://spring.io/projects/spring-authorization-server). + +# About + This project provides support for using Spring Security with OAuth (1a) and OAuth2. It provides features for implementing both consumers and providers of these protocols using standard Spring and Spring Security programming models and configuration idioms. +# Code of Conduct +This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.adoc). +By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io. + # Getting Started -[Dowload](https://github.com/SpringSource/spring-security-oauth/tags) +[Download](https://github.com/spring-projects/spring-security-oauth/tags) or clone from -[GIT](https://github.com/SpringSource/spring-security-oauth) and then -use Maven (2.2.*): +[GIT](https://github.com/spring-projects/spring-security-oauth) and then +use Maven (3.0.\*) and Java (1.6 or better): $ git clone ... $ mvn install -P bootstrap Use the `bootstrap` profile only the first time - it enables some -repositories that can't be exposed in the poms by default. +repositories that can't be exposed in the poms by default. You may +find it useful to add this profile to your local `settings.xml`. + +You need to run Redis to get the build to work. You can install this +using homebrew. Without Redis running the build will lots of Jedis +connection exceptions SpringSource ToolSuite users (or Eclipse users with the latest m2eclipse plugin) can import the projects as existing Maven projects. @@ -24,41 +39,45 @@ Software License Version 2.0 (see license.txt). ## Samples -Samples and integration tests are in [a subdirectory](./samples). -There is a separate README there for orientation and information. -Once you have installed the artifacts locally (as per the getting -started instructions above) you should be able to +Samples and integration tests are in [a subdirectory](samples). There +is a separate README there for orientation and information. Once you +have installed the artifacts locally (as per the getting started +instructions above) you should be able to $ cd samples/oauth2/tonr - $ mvn tomcat:run + $ mvn tomcat7:run -and visit the app in your browser at [http://localhost:8080/tonr/][] +and visit the app in your browser at [http://localhost:8080/tonr2/](http://localhost:8080/tonr2/) to check that it works. (This is for the OAuth 2.0 sample, for the -OAuth 1.0a sample just remove the "2" from the directory path.) +OAuth 1.0a sample just remove the "2" from the directory path.) Integration tests +require slightly different settings for Tomcat so you need to add a profile: + + $ cd samples/oauth2/tonr + $ mvn integration-test -P integration ## Changelog -Lists of issues addressed per release can be found in -[JIRA](https://jira.springsource.org/browse/SECOAUTH#selectedTab=com.atlassian.jira.plugin.system.project%3Aversions-panel). +Lists of issues addressed per release can be found in [github](https://github.com/spring-projects/spring-security-oauth/milestones) (older releases are in +[JIRA](https://jira.spring.io/browse/SECOAUTH/?selectedTab=com.atlassian.jira.jira-projects-plugin:versions-panel)). ## Additional Resources -* [Spring Security OAuth Homepage](http://static.springsource.org/spring-security/oauth) -* [Spring Security OAuth Source](http://github.com/SpringSource/spring-security-oauth) -* [Spring Security OAuth Forum](http://forum.springsource.org/forumdisplay.php?f=79) +* [Spring Security OAuth User Guide](https://projects.spring.io/spring-security-oauth/docs/Home.html) +* [Spring Security OAuth Source](https://github.com/spring-projects/spring-security-oauth) +* [Stackoverflow](https://stackoverflow.com/questions/tagged/spring-security+spring+oauth) # Contributing to Spring Security OAuth Here are some ways for you to get involved in the community: * Get involved with the Spring community on the Spring Community Forums. Please help out on the - [forum](http://forum.springsource.org/forumdisplay.php?f=79) by responding to questions and joining the debate. -* Create [JIRA](https://jira.springsource.org/browse/SECOAUTH) tickets for bugs and new features and comment and + [forum](https://forum.spring.io/forumdisplay.php?f=79) by responding to questions and joining the debate. +* Create [github issues](https://github.com/spring-projects/spring-security-oauth/issues) for bugs and new features and comment and vote on the ones that you are interested in. * Github is for social coding: if you want to write code, we encourage contributions through pull requests from - [forks of this repository](http://help.github.com/forking/). If you want to contribute code this way, please - reference a JIRA ticket as well covering the specific issue you are addressing. -* Watch for upcoming articles on Spring by [subscribing](http://www.springsource.org/node/feed) to springframework.org + [forks of this repository](https://help.github.com/forking/). If you want to contribute code this way, please + reference a github issue as well covering the specific issue you are addressing. +* Watch for upcoming articles on Spring by [subscribing](https://www.springsource.org/node/feed) to springframework.org Before we accept a non-trivial patch or pull request we will need you to sign the [contributor's agreement](https://support.springsource.com/spring_committer_signup). @@ -72,12 +91,12 @@ None of these is essential for a pull request, but they will all help. They can request but before a merge. * Use the Spring Framework code format conventions. Import `eclipse-code-formatter.xml` from the root of the project - if you are using Eclipse. If using IntelliJ, copy `spring-intellij-code-style.xml` to ~/.IntelliJIdea*/config/codestyles + if you are using Eclipse. If using IntelliJ, copy `spring-intellij-code-style.xml` to `~/.IntelliJIdea*/config/codestyles` and select spring-intellij-code-style from Settings -> Code Styles. -* Make sure all new .java files to have a simple Javadoc class comment with at least an @author tag identifying you, and +* Make sure all new .java files have a simple Javadoc class comment with at least an @author tag identifying you, and preferably at least a paragraph on what the class is for. * Add the ASF license header comment to all new .java files (copy from existing files in the project) -* Add yourself as an @author to the .java files that you modify substantially (moew than cosmetic changes). +* Add yourself as an @author to the .java files that you modify substantially (more than cosmetic changes). * Add some Javadocs and, if you change the namespace, some XSD doc elements. * A few unit tests would help a lot as well - someone has to do it. -* If no-one else is using your branch, please rebase it against the current master (or other target branch in the main project). +* If no-one else is using your branch, please rebase it against the current main (or other target branch in the main project). diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..e5a62d0ef --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,4 @@ +redis: + image: redis + ports: + - "6379:6379" diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 4d94ec080..000000000 --- a/docs/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -target -*.ipr -*.iml -*.iws diff --git a/docs/Home.md b/docs/Home.md deleted file mode 100644 index e5acb0740..000000000 --- a/docs/Home.md +++ /dev/null @@ -1,34 +0,0 @@ -# Welcome - -OAuth for Spring Security provides an [OAuth](http://oauth.net) -implementation for -[Spring Security](http://static.springsource.org/spring-security/site/). -Support is provided for the implementation of OAuth providers and -OAuth consumers. There is support for [[Oauth 1(a)|oauth1]] (including -[[two-legged OAuth|twolegged]], a.k.a. "Signed Fetch") and for -[[OAuth 2.0|oauth2]]. - -Applying security to an application is not for the faint of heart, and OAuth is no exception. Before you get started, -you're going to want to make sure you understand OAuth and the problem it's designed to address. There is good -documentation at [the OAuth site](http://oauth.net). You will also want to make sure you understand how -[Spring](http://springframework.org/) and [Spring Security](http://static.springsource.org/spring-security/site/) work. - -You're going to want to be quite familiar with both [OAuth](http://oauth.net) (and/or [OAuth2](http://tools.ietf.org/html/draft-ietf-oauth-v2)) -and [Spring Security](http://static.springsource.org/spring-security/site/), to maximize the effectiveness of this developers guide. OAuth for -Spring Security is tightly tied to both technologies, so the more familiar you are with them, the more likely you'll be to recognize the terminology -and patterns that are used. - -With that, you're ready to get started. Here are some useful links: - -* For access to the binaries, use Maven ([[instructions here|downloads]]) - -* Source code is in github - [at SpringSource/spring-security-oauth](https://github.com/SpringSource/spring-security-oauth). - -* You'll want to see OAuth for Spring Security in action, so here is a -[[tutorial]] - -* Read a more detailed explanation in the [[developer's guide|devguide]]. - -* For more help and support, checkout the [[support links|support]]. - diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md deleted file mode 100644 index 309045857..000000000 --- a/docs/_Sidebar.md +++ /dev/null @@ -1,6 +0,0 @@ -* [[Home]] -* [Tuturial](wiki/tutorial) -* [OAuth 1.0](wiki/oauth1) -* [OAuth 2.0](wiki/oauth2) -* [Downloads](wiki/downloads) -* [Support](wiki/support) diff --git a/docs/devguide.md b/docs/devguide.md deleted file mode 100644 index 2e45399b6..000000000 --- a/docs/devguide.md +++ /dev/null @@ -1,17 +0,0 @@ -# Developers Guide - -## Preparation - -You're going to want to be quite familiar with both [OAuth](http://oauth.net) (and/or [OAuth2](http://tools.ietf.org/html/draft-ietf-oauth-v2)) -and [Spring Security](http://static.springsource.org/spring-security/site/), to maximize the effectiveness of this developers guide. OAuth for -Spring Security is tightly tied to both technologies, so the more familiar you are with them, the more likely you'll be to recognize the terminology -and patterns that are used. - -## Options - -Your first decision is whether you need to leverage support for OAuth 1.0, OAuth 2.0, or both. - -So pick your poison: - -* [[OAuth 1.0|oauth1]] -* [[OAuth 2|oauth2]] \ No newline at end of file diff --git a/docs/downloads.md b/docs/downloads.md deleted file mode 100644 index 67ada27d1..000000000 --- a/docs/downloads.md +++ /dev/null @@ -1,10 +0,0 @@ -# Downloads - -OAuth for Spring Security is a Maven-based project. - -* groupId: `org.springframework.security.oauth` -* artifactId: `spring-security-oauth` - -To download the jars, just look in the [Maven repository][mavenrepo]. - -[mavenrepo]: http://shrub.appspot.com/maven.springframework.org/milestone/org/springframework/security/oauth/spring-security-oauth/ diff --git a/docs/oauth1.md b/docs/oauth1.md deleted file mode 100644 index b4b5ffedf..000000000 --- a/docs/oauth1.md +++ /dev/null @@ -1,266 +0,0 @@ -# OAuth 1 Developers Guide - -## Introduction - -This is the developers guide for the support for OAuth 1.0. For OAuth 2.0, everything is different, so [[see it's developers guide|oauth2]]. - -This user guide is divided into two parts, the first for the OAuth 1.0 provider, the second for the OAuth 1.0 consumer. Here's a -TOC for quick navigation: - -## OAuth 1.0 Provider - -The OAuth 1.0 provider is responsible for managing the OAuth 1.0 consumers that can access its protected resources on behalf of -a user. The provider does this by managing and verifying the OAuth 1.0 tokens that can be used to access the protected -resources. Of course, the provider must also supply an interface for the user to confirm that a consumer can be granted -access to the protected resources (i.e. a confirmation page). - -### Managing Consumers - -The entry point into your database of consumers is defined by the [`ConsumerDetailsService`][ConsumerDetailsService]. -You must define your own [`ConsumerDetailsService`][ConsumerDetailsService] that will load [`ConsumerDetails`][ConsumerDetails] -by the _consumer key_. Note the existence of an [in-memory implementation][InMemoryConsumerDetailsService] of -[`ConsumerDetailsService`][ConsumerDetailsService]. - -When implementing your [`ConsumerDetailsService`][ConsumerDetailsService] consider returning instances of -[BaseConsumerDetails][BaseConsumerDetails] which contains additional information about the consumer that may be useful when -displaying a confirmation screen to the user. - -### Managing Tokens - -The [`OAuthProviderTokenServices`][OAuthProviderTokenServices] interface defines the operations that are necessary to manage -OAuth 1.0 tokens. Note the following: - -* When a request token is created, care must be taken to ensure that it is not an access token. -* When a request token is authorized, the authentication must be stored so that the subsequent access token can reference it. -* When an access token is created, it must reference the authentication that was used to authorized the request token that is used - to create the access token. - -When creating your [`OAuthProviderTokenServices`][OAuthProviderTokenServices] implementation, you may want to consider extending -the [`RandomValueProviderTokenServices`][RandomValueProviderTokenServices] which creates tokens via random value and handles -everything except for the persistence of the tokens. There is also an [in-memory implementation][InMemoryProviderTokenServices] -of the [`OAuthProviderTokenServices`][OAuthProviderTokenServices] that may be suitable, but note that when using the in-memory implementation -a separate thread is spawned to take care of the cleanup of expired tokens. - -### OAuth 1.0 Provider Request Filters - -The requests for the tokens and for access to protected resources are handled by standard Spring Security request filters. The following filters -are required in the Spring Security filter chain in order to implement OAuth 1.0: - -* The [`UnauthenticatedRequestTokenProcessingFilter`][UnauthenticatedRequestTokenProcessingFilter] is used to service the request for - an unauthenticated request token. Default URL: `/oauth_request_token`. -* The [`UserAuthorizationProcessingFilter`][UserAuthorizationProcessingFilter] is used authorize a request token. The user must be - authenticated and it is assumed that the user has been presented with the appropriate confirmation page. -* The [`AccessTokenProcessingFilter`][AccessTokenProcessingFilter] is used to service the request for an OAuth 1.0 access token. - Default URL: `/oauth_access_token`. -* The [`ProtectedResourceProcessingFilter`][ProtectedResourceProcessingFilter] is used to load the Authentication for the request given - an authenticated access token. - -### Managing Nonces - -The OAuth 1.0 spec also recommends that the nonce that is supplied on every OAuth 1.0 request be checked to ensure it isn't used twice for the -same timestamp. In order to do this, nonces must be stored and verified on every OAuth 1.0 request. The interface that is used -to validate nonces is [`OAuthNonceServices`][OAuthNonceServices]. The default implementation, [`ExpiringTimestampNonceServices`][ExpiringTimestampNonceServices], -does not adhere to this recommendation, but only validates that the timestamp isn't too old. If further assurance is required, you will need -to supply your own implementation of `OAuthNonceServices`. Note the existence of an [in-memory implementation][InMemoryNonceServices]. - -### Managing Callbacks - -With the 1.0a revision of the OAuth 1.0 specification, the callback URL is provided at the time the request is made for a request token and will be used when -redirecting the user back to the OAuth 1.0 consumer. Therefore, a means must be provided to persist the callback between requests. The interface that is used -to persist callbacks is [`OAuthCallbackServices`][OAuthCallbackServices]. The default implementation, [`InMemoryCallbackServices`][InMemoryCallbackServices] -persists the callbacks in-memory. You must supply your own implementation of `OAuthCallbackServices` if this is inadequate. - -### Managing Verifiers - -With the 1.0a revision of the OAuth 1.0 specification, the a verifier is provided to the consumer via the user that must be passed back -to the provider when requesting the access token. Therefore, a means must be provided to create and persist the verifier. The interface -that is used to this end is [`OAuthVerifierServices`][OAuthVerifierServices]. The default implementation, -[`RandomValueInMemoryVerifierServices`][RandomValueInMemoryVerifierServices], creates a small, user-friendly (6 readable ASCII characters -by default) verifier and persists the verifier in memory. You must supply your own implementation of `OAuthVerifierServices` if this is inadequate. - -### Authorization By Consumer - -It is sometimes required to limit access to a resource to a specific consumer or to a consumer that has specific roles. The classes in the -[`org.springframework.security.oauth.provider.attributes`][attributes-package] package can be used to do this. Methods can be protected using the -annotations in that package, and the [`ConsumerSecurityConfig`][ConsumerSecurityConfig] can be supplied to the standard Spring Security filter -interceptor in order to enable the annotations. Finally, the [`ConsumerSecurityVoter`][ConsumerSecurityVoter] would need to be supplied to the -Spring Security authentication manager. - -### Provider Configuration - -For the OAuth 1.0 provider, configuration is simplified using the custom spring configuration elements. The schema for these elements rests at -[http://www.springframework.org/schema/security/spring-security-oauth.xsd]. The namespace is `http://www.springframework.org/schema/security/oauth`. - -The following configuration elements are used to supply provider configuration: - -#### The "provider" element - -The `provider` element is used to configure the OAuth 1.0 provider mechanism. The following attributes can be applied to the `provider` element: - -* `consumer-details-service-ref`: The reference to the bean that defines the consumer details service. This is required if not autowired. -* `token-services-ref`: The reference to the bean that defines the token services. -* `request-token-url`: The URL at which a request for an unauthenticated request token will be serviced. Default value: "/oauth_request_token" -* `authenticate-token-url`: The URL at which a request to authenticate a request token will be serviced. Default value: "/oauth_authenticate_token" -* `access-token-url`: The URL at which a request for an access token (using an authenticated request token) will be serviced. Default value: "/oauth_access_token" -* `access-granted-url`: The URL to which the user will be redirected upon authenticating a request token, but only if there was no callback URL supplied from the oauth consumer. Default value: "/" -* `user-approval-url`: The URL to which the user will be redirected if for some reason authentication of a request token failed. Default behavior is to just issue a "401: unauthorized" response. -* `nonce-services-ref`: The reference to the bean that defines the nonce services. Default is to supply an instance of `org.springframework.security.oauth.provider.nonce.ExpiringTimestampNonceServices` -* `callback-services-ref`: The reference to the bean that defines the callback services. Default is to supply an instance of `org.springframework.security.oauth.provider.callback.InMemoryCallbackServices` -* `verifier-services-ref`: The reference to the bean that defines the verifier services. Default is to supply an instance of `org.springframework.security.oauth.provider.verifier.RandomValueInMemoryVerifierServices` -* `auth-handler-ref`: The reference to the bean that defines the authentication handler. Default is to supply an instance of `org.springframework.security.oauth.provider.DefaultAuthenticationHandler` -* `support-ref`: The reference to the bean that defines the provider support logic. Default is to supply an instance of `org.springframework.security.oauth.provider.CoreOAuthProviderSupport` -* `token-id-param`: The name of the request parameter that specifies to the 'authenticate-token-url' the id of the token that is to be authenticated. Default value: "requestToken". -* `callback-url-param`: The name of the request parameter that specifies to the 'authenticate-token-url' the callback URL to which the user is to be redirected upon successful authentication. Default value: "callbackURL". - -#### The "consumer-details-service" element - -The `consumer-details-service` element is used to define an in-memory implementation of the consumer details service. It takes an `id` attribute and an -arbitrary number of `consumer` child elements that define the following attributes for each consumer: - -* `key`: (required) The consumer key. -* `secret`: (required) The consumer secret. -* `name`: The (display) name of the consumer. -* `authorities`: Comma-separated list of authorities (e.g. roles) that are granted to the consumer. -* `resourceName`: The name of the resource. -* `resourceDescription`: The description of the resource. -* `requiredToObtainAuthenticatedToken`: Whether this consumer is required to obtain an authenticated oauth token. If _true_, it means that the OAuth 1.0 consumer won't be granted access to the protected resource unless the user is directed to the token authorization page. If _false_, it means that the provider has an additional level of trust with the consumer. Not requiring an authenticated access token is also known as "2-legged" OAuth or "signed fetch". For more information, see [two-legged OAuth](./twolegged.html). - -#### The "token-services" element - -The `token-services` element is a simple element that can be used to provide an in-memory implementation of the provider token services. -It supports an _id_ attribute (bean id) and a _cleanupInterval_ attribute that specifies how often the cleanup thread should wake up (in seconds). - -#### The "verifier-services" element - -The `verifier-services` element is a simple element that can be used to provide an in-memory implementation of the provider verifier services. -It supports an `id` attribute (bean id) and a `verifierLengthBytes` attribute that specifies the length of the verifier. - -### Configuring An OAuth-Aware Expression Handler - -You may want to take advantage of Spring Security's [expression-based access control](http://static.springsource.org/spring-security/site/docs/3.0.x/reference/el-access.html). -You can register a oauth-aware expression handler with the `expression-handler` element. Use the id of the oauth expression handler to add oauth-aware -expressions to the built-in expressions. - -The expressions include _oauthConsumerHasRole_, _oauthConsumerHasAnyRole_, and _denyOAuthConsumer_ which can be used to provide access based on the role of the -oauth consumer. - -## OAuth 1.0 Consumer - -The OAuth 1.0 consumer logic is responsible for (1) obtaining an OAuth 1 access token and (2) signing requests for OAuth 1 -protected resources. OAuth for Spring Security provides a request filter for acquiring the access token, a request filter -for ensuring that access to certain URLs is locked down to a set of acquired access token, and utilities for making a request -for a protected resource. A consumer must be responsible for maintaing a list of protected resources that can be accessed and, -like the provider, a consumer must be responsible for managing the OAuth 1.0 tokens. - -If you were discouraged by the complexity of implementing an OAuth 1.0 provider, take heart. Implementation of an OAuth 1.0 -consumer is easier, partially because OAuth 1.0 for Spring Security provides suitable defaults for most cases. - -### Managing Protected Resources - -A database of protected resources that are accessible by a consumer must be provided through the [`ProtectedResourceDetailsService`][ProtectedResourceDetailsService]. -Each protected resource must provide all information related to obtaining access to it. This includes the URL to obtain a request token, the URL to which to -redirect the user for authorization, the URL at which to obtain an access token, etc. It also contains various properties that describe the provider of the -protected resource. Consider the existence of the [`InMemoryProtectedResourceDetailsService`][InMemoryProtectedResourceDetailsService] -and the [`BaseProtectedResourceDetails`][BaseProtectedResourceDetails] for help in creating the database of protected resources. - -### Managing Provider Tokens - -Like the provider, the consumer must be responsible for managing the OAuth tokens. The necessary interface for managing the consumer tokens is -[`OAuthConsumerTokenServices`][OAuthConsumerTokenServices]. Assuming that the consumer can leverage an active HTTP session, the default -[`HttpSessionBasedTokenServices`][HttpSessionBasedTokenServices] might be adequate, but if you'd like to persist access tokens longer than a user -session, you'll have to implement your own persistent implementation of the token services. - -### OAuth 1.0 Consumer Request Filters - -There are two request filters that are applicable to the OAuth consumer logic. The first filter, [`OAuthConsumerContextFilter`][OAuthConsumerContextFilter], -is responsible for establishing an OAuth-specific security context, very similar to Spring Security's `SecurityContext`. The security -context simply contains a set of access tokens that have been obtained for the current user. This security context is leveraged when making requests -for protected resources. - -There is another request filter, [`OAuthConsumerProcessingFilter`][OAuthConsumerProcessingFilter], that can be applied to specific URLs or -URL patterns that require access to a remote protected resource. Putting this filter in Spring Security's filter chain -will ensure that any access tokens needed for the specified URL patters will be obtained before allowing access to the resources. - -### Requesting Protected Resources - -The [`OAuthRestTemplate`][OAuthRestTemplate] can be used to make REST-like requests to resources protected by OAuth. It's used just like a standard -RestTemplate (new in Spring 3), but is supplied with a specific `ProtectedResourcDetails` so it can sign its requests. - -### Consumer Configuration - -For the OAuth 1.0 consumer, configuration is simplified using the custom spring configuration elements. The schema for these elements rests at -[http://www.springframework.org/schema/security/spring-security-oauth.xsd](http://www.springframework.org/schema/security/spring-security-oauth.xsd). -The namespace is `http://www.springframework.org/schema/security/oauth`. - -Two custom configuration elements are used to supply provider configuration: - -#### The "consumer" element - -The `consumer` element configures the OAuth 1.0 consumer mechanism. This element is used to set up the security filter(s) that will handle -the OAuth consumer logic. The OAuth context filter establishes a context for the OAuth consumer logic. The OAuth access filter is used to -apply OAuth constraints on specified URLs (request paths) in your application. The access filter is applied by specified one or more `url` -child elements to the `consumer` element. - -The `url` element supports the following attributes: - -* `pattern`: (required) The URL pattern. -* `resources`: (required) Comma-separated list of the ids of the protected resources that the URL requires access to. -* `httpMethod`: The HTTP method that requires access. Default is all methods. - -The `consumer` element also supports the following attributes: - -* `resource-details-service-ref`: The reference to the resource details service. This is required if not autowired. -* `oauth-failure-page`: The page to which to redirect the user if a problem happens during OAuth 1.0 authentication. -* `entry-point-ref`: Reference to the entry point to use if a problem happens during OAuth 1.0 authentication (overrides _oauth-failure-page_). -* `path-type`: URL path type. Default value: "ant". -* `lowercase-comparisons`: Whether to use lowercase comparisons. -* `support-ref`: Reference to the OAuth 1.0 consumer support logic. -* `token-services-factory-ref`: Reference to the token services factory. - -#### The "resource-details-service" element - -The `resource-details-service` element configures an in-memory implementation of the resource details. It supports an "id" attribute -and an arbitrary number of `resource` child elements which are used to define the protected resources and support the following attributes: - -* `id`: (required) The resource id. -* `key`: (required) The consumer key. -* `secret`: (required) The shared secret. -* `request-token-url`: (required) The URL to use to get the OAuth 1.0 request token. -* `user-authorization-url`: (required) The URL to which to redirect the user to authorize the request token. -* `access-token-url`: (required) The URL to use to get an OAuth 1.0 access token. -* `signature-method`: The signature method to use (e.g. "HMAC-SHA1", "PLAINTEXT", etc.). Default "HMAC-SHA1". -* `user-authorization-token-param`: Name of the request parameter to use to pass the value of the request token when redirecting the user to the authorization page. Default value: "requestToken" -* `user-authorization-callback-param`: Name of the request parameter to use to pass the value of the callback URL when redirecting the user to the authorization page. Default value: "callbackURL" -* `accepts-authorization-header`: Whether the provider accepts the HTTP authorization header. Default: "true" -* `authorization-header-realm`: The "realm" for the HTTP authorization header. -* `use10a`: Whether the resource is protected using OAuth 1.0a. Default: "true" - -[ConsumerDetailsService]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/ConsumerDetailsService.html -[ConsumerDetails]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/ConsumerDetails.html -[InMemoryConsumerDetailsService]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/InMemoryConsumerDetailsService.html -[BaseConsumerDetails]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/BaseConsumerDetails.html -[OAuthProviderTokenServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/token/OAuthProviderTokenServices.html -[RandomValueProviderTokenServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/token/RandomValueProviderTokenServices.html -[InMemoryProviderTokenServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/token/InMemoryProviderTokenServices.html -[UnauthenticatedRequestTokenProcessingFilter]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/UnauthenticatedRequestTokenProcessingFilter.html -[UserAuthorizationProcessingFilter]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/UserAuthorizationProcessingFilter.html -[AccessTokenProcessingFilter]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/AccessTokenProcessingFilter.html -[ProtectedResourceProcessingFilter]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/ProtectedResourceProcessingFilter.html -[OAuthNonceServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/nonce/OAuthNonceServices.html -[ExpiringTimestampNonceServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/nonce/ExpiringTimestampNonceServices.html -[InMemoryNonceServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/nonce/InMemoryNonceServices.html -[OAuthCallbackServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/callback/OAuthCallbackServices.html -[InMemoryCallbackServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/callback/InMemoryCallbackServices.html -[OAuthVerifierServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/verifier/OAuthVerifierServices.html -[RandomValueInMemoryVerifierServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/verifier/RandomValueInMemoryVerifierServices.html -[attributes-package]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/attributes/package-summary.html -[ConsumerSecurityConfig]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/attributes/ConsumerSecurityConfig.html -[ConsumerSecurityVoter]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/attributes/ConsumerSecurityVoter.html -[ProtectedResourceDetailsService]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/consumer/ProtectedResourceDetailsService.html -[InMemoryProtectedResourceDetailsService]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/consumer/InMemoryProtectedResourceDetailsService.html -[BaseProtectedResourceDetails]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/consumer/BaseProtectedResourceDetails.html -[OAuthConsumerTokenServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/consumer/token/OAuthConsumerTokenServices.html -[HttpSessionBasedTokenServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/consumer/token/HttpSessionBasedTokenServices.html -[OAuthConsumerContextFilter]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/consumer/OAuthConsumerContextFilter.html -[OAuthConsumerProcessingFilter]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/consumer/OAuthConsumerProcessingFilter.html -[OAuthRestTemplate]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/consumer/OAuthRestTemplate.html \ No newline at end of file diff --git a/docs/oauth2.md b/docs/oauth2.md deleted file mode 100644 index 84086bd70..000000000 --- a/docs/oauth2.md +++ /dev/null @@ -1,201 +0,0 @@ -# OAuth 2 Developers Guide - -## Introduction - -This is the user guide for the support for [`OAuth 2.0`](http://tools.ietf.org/html/draft-ietf-oauth-v2). For OAuth 1.0, everything is different, so [see it's user guide][oauth1]. - -This user guide is divided into two parts, the first for the OAuth 2.0 provider, the second for the OAuth 2.0 client. - -## OAuth 2.0 Provider - -The OAuth 2.0 provider mechanism is responsible for exposing OAuth 2.0 protected resources. The configuration involves establishing the OAuth 2.0 clients that can access its protected resources on behalf of a user. The provider does this by managing and verifying the OAuth 2.0 tokens that can be used to access the protected resources. Where applicable, the provider must also supply an interface for the user to confirm that a client can be granted access to the protected resources (i.e. a confirmation page). - -### Managing Clients - -The entry point into your database of clients is defined by the [`ClientDetailsService`][ClientDetailsService]. You must define your own `ClientDetailsService` that will load [`ClientDetails`][ClientDetails] by the . Note the existence of an [in-memory implementation][InMemoryClientDetailsService] of `ClientDetailsService`. - -When implementing your `ClientDetailsService` consider returning instances of (or extending) [`BaseClientDetails`][BaseClientDetails]. - -### Managing Tokens - -The [`AuthorizationServerTokenServices`][AuthorizationServerTokenServices] interface defines the operations that are necessary to manage OAuth 2.0 tokens. Note the following: - -* When an access token is created, the authentication must be stored so that the subsequent access token can reference it. -* The access token is used to load the authentication that was used to authorize its creation. - -When creating your `AuthorizationServerTokenServices` implementation, you may want to consider using the [`RandomValueTokenServices`][RandomValueTokenServices] which creates tokens via random value and handles everything except for the persistence of the tokens which it delegates to a `TokenStore`. - -There is an [in-memory implementation][InMemoryTokenStore] of the `TokenStore` that may be suitable. - -## OAuth 2.0 Provider Implementation - -The provider role in OAuth 2.0 is actually split between Authorization Service and Resource Service, and while these sometimes reside in the same application, with Spring Security OAuth you have the option to split them across two applications, and also to have multiple Resource Services that share an Authorization Service. The requests for the tokens are handled by Spring MVC controller endpoints, and access to protected resources is handled by standard Spring Security request filters. The following endpoints are required in the Spring Security filter chain in order to implement OAuth 2.0 Authorization Server: - -* [`AuthorizationEndpoint`][AuthorizationEndpoint] is used to service requests for authorization. Default URL: `/oauth/authorize`. -* [`TokenEndpoint`][TokenEndpoint] is used to service requests for access tokens. Default URL: `/oauth/token`. - -The following filters are required to implement an OAuth 2.0 Resource Server: - -* The [`OAuth2ExceptionHandlerFilter`][OAuth2ExceptionHandlerFilter] is used to handle any errors. -* The [`OAuth2AuthenticationProcessingFilter`][OAuth2AuthenticationProcessingFilter] is used to load the Authentication for the request given an authenticated access token. - -For all the OAuth 2.0 provider features, configuration is simplified using the custom spring configuration elements. The schema for these elements rests at [http://www.springframework.org/schema/security/spring-security-oauth2.xsd][oauth2.xsd]. The namespace is `http://www.springframework.org/schema/security/oauth2`. - -## Authorization Server Configuration - -As you configure the Authorization Server, you have to consider the grant type that the client is to use to obtain an access token from the end-user (e.g. authorization code, user credentials, refresh token). The configuration of the server is used to provide implementations of the client details service and token services and to enable or disable certain aspects of the mechanism globally. Note, however, that each client can be configured specifically with permissions to be able to use certain authorization mechanisms and access grants. I.e. just because your provider is configured to support the "client credentials" grant type, doesn't mean that a specific client is authorized to use that grant type. - -The `` element is used to configure the OAuth 2.0 Authorization Server mechanism. The following attributes can be applied to the `authorization-server` element: - -* `client-details-service-ref`: The reference to the bean that defines the client details service. -* `token-services-ref`: The reference to the bean that defines the token services. - -An important aspect of the provider configuration is the way that a authorization code is supplied to an OAuth client. A authorization code is obtained by the OAuth client by directing the end-user to an authorization page where the user can enter her credentials, resulting in a redirection from the provider authorization server back to the OAuth client with the authorization code. Examples of this are elaborated in the OAuth 2 specification. - -### Grant Types - -The authorization code grant type is configured via the `authorization-code` child element of the `authorization-server` element. The `authorization-code` element supports the following attributes: - -* `disabled`: Boolean value specifying whether the authorization code mechanism is disabled. This effectively disables the authorization code grant mechanism. -* `services-ref`: The reference to the bean that defines the authorization code services (instance of `org.springframework.security.oauth2.provider.code.AuthorizationCodeServices`) -* `user-approval-page`: The URL of the page that handles the user approval form. -* `approval-parameter-name`: The name of the form parameter that is used to indicate user approval of the client authentication request. - -Other grant types are also included as child elements of the `authorization-server`. - -### Configuring Client Details - -The `client-details-service` element is used to define an in-memory implementation of the client details service. It takes an `id` attribute and an arbitrary number of `client` child elements that define the following attributes for each client: - -* `client-id`: (required) The client id. -* `secret`: (required) The client secret, if any. -* `scope`: The scope to which the client is limited (comma-separated). If scope is undefined or empty (the default) the client is not limited by scope. -* `authorized-grant-types`: Flows that are authorized for the client to use (comma-separated). Default value is "web\_server". -* `authorities`: Authorities that are granted to the client (comma-separated). - -### Configuring the Endpoint URLs - -The `` element has some attributes that can be used to change the default endpoint URLs: - -* `authorization-endpoint-url`: The URL at which a request for an authorization will be serviced (defaults to `/oauth/authorize`). This URL should be protected using Spring Security so that it is only accessible to authenticated users. -* `token-endpoint-url`: The URL at which a request for an access token will be serviced (defaults to `/oauth/token`). This URL should be accessible to anonymous users. - -If the endpoint URLs are changed in this way via the namespace, then an extra bean definition for a servlet Filter is created with id `oauth2EndpointUrlFilter`. This has to be mapped in your servlet container so that incoming requests with those paths are recognized by the Spring dispatcher servlet. The filter definition in `web.xml` would look like this: - - - oauth2EndpointUrlFilter - org.springframework.web.filter.DelegatingFilterProxy - - contextAttribute - org.springframework.web.servlet.FrameworkServlet.CONTEXT.spring - - - - - oauth2EndpointUrlFilter - /* - - - -This filter has to be applied in the right order, so make sure the mapping appears in `web.xml` _before_ the mapping for the Spring Security filter. - -### Configuring An OAuth-Aware Expression Handler - -You may want to take advantage of Spring Security's [expression-based access control][expressions]. You can register a oauth-aware expression handler with the `expression-handler` element. Use the id of the oauth expression handler to add oauth-aware expressions to the built-in expressions. - -The expressions include _oauth2.clientHasRole_, _oauth2.clientHasAnyRole_, and _oath2.denyClient_ which can be used to provide access based on the role of the oauth client. - -## Resource Server Configuration - -You need to supply the `` element with an `id` attribute - this is the bean id for a servlet `Filter` that can be added to teh standard Spring Security chain, e.g. - - - - ... - - - - - - -The following attributes can be applied to the `resource-server` element: - -* `token-services-ref`: The reference to the bean that defines the token services. -* `resource-id`: The id for the resource (optional, but recommended and will be validated by the auth server if present) - -## OAuth 2.0 Client - -The OAuth 2.0 client mechanism is responsible for access the OAuth 2.0 protected resources of other servers. The configuration involves establishing the relevant protected resources to which users might have access. The client also needs to be supplied with mechanisms for storing authorization codes and access tokens for users. - -### Managing Protected Resources - -The entry point into your database of protected resources is defined by the [`OAuth2ProtectedResourceDetailsService`][OAuth2ProtectedResourceDetailsService]. You must define your own `OAuth2ProtectedResourceDetailsService` that will load [`OAuth2ProtectedResourceDetails`][OAuth2ProtectedResourceDetails] by id. Note the existence of an [in-memory implementation][InMemoryOAuth2ProtectedResourceDetailsService] of `OAuth2ProtectedResourceDetailsService`, which might be adequate for your needs. See "Configuring Resource Details" for more information. - -### Managing Tokens - -The [`OAuth2ClientTokenServices`][OAuth2ClientTokenServices] interface defines the operations that are necessary to manage OAuth 2.0 tokens for specific users. There is an in-memory implementation provided, but it's likely you'll need to implement your own service for storing the access tokens and associated authentication instances in a persistent database. - -### Client Configuration - -For the OAuth 2.0 client, configuration is simplified using the custom spring configuration elements. The schema for these elements rests at [http://www.springframework.org/schema/security/spring-security-oauth2.xsd][oauth2.xsd]. The namespace is `http://www.springframework.org/schema/security/oauth2`. You need to supply the `` element with an `id` attribute - this is the bean id for a servlet `Filter` that can be added to the standard Spring Security chain, e.g. - - - - ... - - - - - -The `client` element is used to configure the OAuth 2.0 client mechanism. The following attributes can be applied to the `client` element: - -* `token-services-ref`: The reference to the bean that stores tokens on behalf of a user. Default value is an instance of [`InMemoryOAuth2ClientTokenServices`][InMemoryOAuth2ClientTokenServices]. -* `resource-details-service-ref`: The reference to the bean that services the known resource details. - -### Protected Resource Configuration - -Protected resources can be defined using the `resource` configuration element. Each `resource` element is effectively a definition of a bean that is an instance of [`OAuth2ProtectedResourceDetails`][OAuth2ProtectedResourceDetails]. The `resource` element supports the following attributes: - -* `id`: The id of the resource. The id is only used by the client to lookup the resource; it's never used in the OAuth protocol. It's also used as the id of the bean. -* `type`: The type (i.e. "grant type") of the resource. This is used to specify how an access token is to be obtained for this resource. Valid values include "authorization\_code", "password", and "assertion". Default value is "authorization\_code". -* `client-id`: The OAuth client id. This is the id by with the OAuth provider is to identify your client. -* `client-secret`: The secret associated with the resource. By default, no secret will be supplied for access to the resource. -* `access-token-uri`: The URI of the provider OAuth endpoint that provides the access token. -* `user-authorization-uri`: The uri to which the user will be redirected if the user is ever needed to authorize access to the resource. Note that this is not always required, depending on which OAuth 2 profiles are supported. -* `scope`: Comma-separted list of string specifying the scope of the access to the resource. By default, no scope will be specified. -* `client-authentication-scheme`: The scheme used by your client to authenticate to the access token endpoint. Suggested values: "http\_basic" and "form". Default: "http\_basic". See section 2.1 of the OAuth 2 spec. - -### Accessing Protected Resources - -Once you've supplied all the configuration for the resources, you can now access those resources. The suggested method for accessing those resources is by using [the `RestTemplate` introduced in Spring 3][restTemplate]. OAuth for Spring Security has provided [an extension of RestTemplate][OAuth2RestTemplate] that only needs to be supplied an instance of [`OAuth2ProtectedResourceDetails`][OAuth2ProtectedResourceDetails]. To use it with user-tokens (authorization code grants) you should consider using the XML namespace shortcut `` which creates some request and session scoped context objects so that requests for different users do not collide at runtime. - -## Customizations for Clients of External OAuth2 Providers - -Some external OAuth2 providers (e.g. [Facebook][Facebook]) do not quite implement the specification correctly, or else they are just stuck on an older version of the spec than Spring Security OAuth. To use those providers in your client application you might need to adapt various parts of the client-side infrastructure. - -To use Facebook as an example, there is a Facebook feature in the `tonr2` application (you need to change the configuration to add your own, valid, client id and secret - they are easy to generate on the Facebook website). - -Facebook token responses also contain a non-compliant JSON entry for the expiry time of the token (they use `expires` instead of `expires_in`), so if you want to use the expiry time in your application you will have to decode it manually using a custom `OAuth2SerializationService`. - - [oauth1]: https://github.com/SpringSource/spring-security-oauth/wiki/oauth1.html "OAuth 1.0a support" - [AuthorizationEndpoint]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpoint.html "AuthorizationEndpoint" - [TokenEndpoint]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/endpoint/TokenEndpoint.html "TokenEndpoint" - [RandomValueTokenServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/token/RandomValueOAuth2ProviderTokenServices.html "RandomValueTokenServices" - [InMemoryTokenStore]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/token/InMemoryTokenStore.html "InMemoryTokenStore" - [ClientDetailsService]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/ClientDetailsService.html "ClientDetailsService" - [ClientDetails]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/ClientDetails.html "ClientDetails" - [InMemoryClientDetailsService]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/InMemoryClientDetailsService.html "InMemoryClientDetailsService" - [BaseClientDetails]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/BaseClientDetails.html "BaseClientDetails" - [AuthorizationServerTokenServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/token/AuthorizationServerTokenServices.html "AuthorizationServerTokenServices" - [OAuth2ExceptionHandlerFilter]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/filter/OAuth2ExceptionHandlerFilter.html "OAuth2ExceptionHandlerFilter" - [OAuth2AuthenticationProcessingFilter]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/filter/OAuth2AuthenticationProcessingFilter.html "OAuth2AuthenticationProcessingFilter" - [oauth2.xsd]: http://www.springframework.org/schema/security/spring-security-oauth2.xsd "oauth2.xsd" - [expressions]: http://static.springsource.org/spring-security/site/docs/3.0.x/reference/el-access.html "Expression Access Control" - [OAuth2ProtectedResourceDetailsService]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/client/OAuth2ProtectedResourceDetailsService.html "OAuth2ProtectedResourceDetailsService" - [InMemoryOAuth2ProtectedResourceDetailsService]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/client/InMemoryOAuth2ProtectedResourceDetailsService.html "InMemoryOAuth2ProtectedResourceDetailsService" - [InMemoryOAuth2ClientTokenServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/client/token/service/InMemoryOAuth2ConsumerTokenServices.html "InMemoryOAuth2ClientTokenServices" - [OAuth2ClientTokenServices]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/client/token/OAuth2ClientTokenServices.html "OAuth2ClientTokenServices" - [restTemplate]: http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/client/RestTemplate.html "RestTemplate" - [OAuth2RestTemplate]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/client/OAuth2RestTemplate.html "OAuth2RestTemplate" - [OAuth2ProtectedResourceDetails]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth2/client/OAuth2ProtectedResourceDetails.html "OAuth2ProtectedResourceDetails" - [Facebook]: http://developers.facebook.com/docs/authentication "Facebook" diff --git a/docs/support.md b/docs/support.md deleted file mode 100644 index 56bffc2f3..000000000 --- a/docs/support.md +++ /dev/null @@ -1,7 +0,0 @@ -# Support - -Questions about OAuth for Spring Security can be posed at the [forum at SpringSource](http://forum.springsource.org/forumdisplay.php?f=79). -To report bugs, submit enchancement requests or add something to the wish list, use [JIRA](https://jira.springsource.org/browse/SECOAUTH). - -Commercial support is available through [Web Cohesion](http://www.webcohesion.com). To inquire about commercial support send an email to -"info at webcohesion dot com". \ No newline at end of file diff --git a/docs/tutorial.md b/docs/tutorial.md deleted file mode 100644 index 99ffcc1d6..000000000 --- a/docs/tutorial.md +++ /dev/null @@ -1,84 +0,0 @@ -# Tutorial - -## Introduction - -There's a good [getting started guide](http://www.hueniverse.com/hueniverse/2007/10/beginners-gui-1.html) that illustrates OAuth -1.0 by describing two different (but related) services. One is a photo-sharing application. The other is a photo-printing -application. In OAuth terms, the photo sharing application is the OAuth _provider_ and the photo printing application -is the OAuth _consumer_ or _client_. - -For this tutorial, we will see OAuth for Spring Security in action by deploying a photo-sharing application and a -photo-printing application on our local machine. We'll name the photo-sharing application "Sparklr" and the -photo-printing application "Tonr". A user named "Marissa" (who has an account at both Sparkr and Tonr) will use Tonr -to access her photos on Sparklr without ever giving Tonr her credentials to Sparklr. - -There is a Sparklr application for both OAuth 1.0 and for OAuth 2.0, likewise Tonr. Download the pair for the spec that you'd like to to see -in action: - -OAuth 1.0|OAuth 2.0 ----------|--------- -[Sparklr 1](http://static.springsource.org/spring-security/oauth/sparklr.zip) | [Sparklr 2](http://static.springsource.org/spring-security/oauth/sparklr2.zip) -[Tonr 1](http://static.springsource.org/spring-security/oauth/tonr.zip) | [Tonr 2](http://static.springsource.org/spring-security/oauth/tonr2.zip) - -Each application is a standard [Maven](http://maven.apache.org/) project, so you will need Maven installed. Each application -is a standard Spring MVC application with Spring Security integrated. Presumably, you're familiar with Spring and Spring Security so -the configuration files will look familiar to you. - -## Setup - -Unzip the Sparklr and Tonr applications, and take a look around. Note especially the Spring configuration files in `src/main/webapp/WEB-INF`. - -For Sparklr, you'll notice the definition of the OAuth provider mechanism and the consumer/client details along with the -[standard spring security configuration](http://static.springsource.org/spring-security/site/docs/3.0.x/reference/ns-config.html) elements. For Tonr, -you'll notice the definition of the OAuth consumer/client mechanism and the resource details. For more information about the necessary -components of an OAuth provider and consumer, see the [[developers guide|devguide]]. - -You'll also notice the Spring Security filter chain in `applicationContext.xml` and how it's configured for OAuth support. - -### Deploy Sparklr - -``` -mvn install -cd samples/oauth(2)/sparklr -mvn tomcat:run -``` - -Sparklr should be started on port 8080. Go ahead and browse to [http;//localhost:8080/sparklr](http;//localhost:8080/sparklr). Note the basic -login page and the page that can be used to browse Marissa's photos. Logout to ensure Marissa's session is no longer valid. (Of course, -the logout isn't mandatory; an active Sparklr session will simply bypass the step that prompts for Marissa's credentials before -confirming authorization for Marissa's protected resources.) - -### Start Tonr. - -Shutdown sparklr (it will be launched in the same container when tonr runs), then - -``` -mvn install -cd samples/oauth(2)/tonr -mvn tomcat:run -``` - -Tonr should be started on port 8080. Browse to [http://localhost:8080/tonr(2)](http://localhost:8080/tonr). Note Tonr's home page has a '2' on the end if it is the oauth2 version. - -### Observe... - -Now that you've got both applications deployed, you're ready to observe OAuth in action. - -1. Login to Tonr. - - Marissa's credentials are already hardcoded into the login form. - -2. Click to view Marissa's Sparklr photos. - - You will be redirected to the Sparklr site where you will be prompted for Marissa's credentials. - -3. Login to Sparklr. - - Upon successful login, you will be prompted with a confirmation screen to authorize access to Tonr - for Marissa's pictures. - -4. Click "authorize". - - Upon authorization, you should be redirected back to Tonr where Marissa's Sparklr photos are displayed - (presumably to be printed). - diff --git a/docs/twolegged.md b/docs/twolegged.md deleted file mode 100644 index 87880bf44..000000000 --- a/docs/twolegged.md +++ /dev/null @@ -1,26 +0,0 @@ -# 2-Legged OAuth - -Two-legged OAuth (also known as "signed fetch") is basically OAuth without the user. It's a way for a consumer (i.e. client) to make a signed request -to a provider (i.e. server) by leveraging the OAuth signature algorithm. This means that the provider has an extra level of trust with the consumer and will -therefore provide data to the consumer without making an end-user authorize a token. - -This has particular applicability to gadget frameworks. For example, [OpenSocial](http://www.opensocial.org/) platforms often use 2-legged OAuth so gadget -developers can have the gadget (the OAuth consumer) make Web service requests to their remote server (the OAuth provider). Since the gadget developer and -the server developer are often the same entity, the server can trust the gadget without the need for the gadget to obtain special permission from the user to -access the user's data. - -To implement 2-legged OAuth using _OAuth for Spring Security_, all that is needed is for the provider to indicate that a specific consumer has an extra -level of trust. To do this, make sure your implementation of [`ConsumerDetailsService`][ConsumerDetailsService] returns instances of -[`ConsumerDetails`][ConsumerDetails] that implement [`ExtraTrustConsumerDetails`][ExtraTrustConsumerDetails]. Then, for each consumer -that doesn't need to obtain a user-authorized token, make sure [`ExtraTrustConsumerDetails.isRequiredToObtainAuthenticatedToken()`][isRequiredToObtainAuthenticatedToken] -returns `false`. - -In many instances, providers may want to manage the authentication that is set up in the security context. By default for 2-legged OAuth, only the consumer's -authentication will be set up in the context. However, if a user authentication is needed in the context, provide an alternate implementation of -`org.springframework.security.oauth.provider.OAuthAuthenticationHandler` that loads the user authentication, and provide a reference to the alternate -implementation using the "auth-handler-ref" attribute of the "provider" configuration element. - -[ConsumerDetailsService]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/ConsumerDetailsService.html -[ConsumerDetails]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/ConsumerDetails.html -[ExtraTrustConsumerDetails]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/ExtraTrustConsumerDetails.html -[isRequiredToObtainAuthenticatedToken]: http://static.springsource.org/spring-security/oauth/apidocs/org/springframework/security/oauth/provider/ExtraTrustConsumerDetails.html#isRequiredToObtainAuthenticatedToken() diff --git a/etc/nohttp/checkstyle.xml b/etc/nohttp/checkstyle.xml new file mode 100644 index 000000000..4b2ef2e48 --- /dev/null +++ b/etc/nohttp/checkstyle.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/etc/nohttp/whitelist.lines b/etc/nohttp/whitelist.lines new file mode 100644 index 000000000..55860ea4f --- /dev/null +++ b/etc/nohttp/whitelist.lines @@ -0,0 +1 @@ +http://junit.sourceforge.net/javadoc/ \ No newline at end of file diff --git a/license.txt b/license.txt index 261eeb9e9..20e4bd856 100755 --- a/license.txt +++ b/license.txt @@ -1,6 +1,6 @@ Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ + https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -192,7 +192,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + 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, diff --git a/mvnw b/mvnw new file mode 100755 index 000000000..02f96acef --- /dev/null +++ b/mvnw @@ -0,0 +1,243 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +VERSION=$(awk '/ 0) {$0=$0} 1' `dirname $0`/pom.xml| grep '\(.*\)<.*/\1/') +if echo $VERSION | egrep -q 'M|RC'; then + echo Activating \"milestone\" profile for version=\"$VERSION\" + echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone" +else + echo Deactivating \"milestone\" profile for version=\"$VERSION\" + echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//') +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} ${MAVEN_ARGS} "$@" + diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 000000000..eb9a292a7 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,145 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/notice.txt b/notice.txt index 77391586a..9007f5823 100755 --- a/notice.txt +++ b/notice.txt @@ -5,16 +5,16 @@ ====================================================================== This product includes software developed by -the Apache Software Foundation (http://www.apache.org). +the Apache Software Foundation (https://www.apache.org). This product includes software developed by the Spring Framework -Project (http://www.springframework.org). +Project (https://www.springframework.org). The end-user documentation included with a redistribution, if any, must include the following acknowledgement: "This product includes software developed by Web Cohesion - (http://www.webcohesion.com)." + (https://www.webcohesion.com)." Alternately, this acknowledgement may appear in the software itself, if and wherever such third-party acknowledgements normally appear. diff --git a/pom.xml b/pom.xml index 67c09375a..c1837e581 100644 --- a/pom.xml +++ b/pom.xml @@ -1,42 +1,48 @@ - + 4.0.0 org.springframework.security.oauth spring-security-oauth-parent OAuth for Spring Security Parent Project for OAuth Support for Spring Security pom - 1.0.1.BUILD-SNAPSHOT - http://static.springframework.org/spring-security/oauth + 2.5.3.BUILD-SNAPSHOT + https://docs.spring.io/spring-security/oauth spring-security-oauth spring-security-oauth2 + tests samples + UTF-8 - 3.1.2.RELEASE - 3.1.3.RELEASE - [3.1.0,4.0.0) - [3.1.0,4.0.0) + 1.14 + 4.3.30.RELEASE + 4.2.20.RELEASE + 1.5.2.RELEASE + 2.6.3 + 4.12 + 1.10.19 + 1.6 - http://github.com/SpringSource/spring-security-oauth + https://github.com/SpringSource/spring-security-oauth scm:git:git://github.com/SpringSource/spring-security-oauth.git scm:git:ssh://git@github.com/SpringSource/spring-security-oauth.git HEAD JIRA - http://opensource.atlassian.com/projects/spring/browse/SECOAUTH + https://opensource.atlassian.com/projects/spring/browse/SECOAUTH Spring Security OAuth Forum - http://forum.springframework.org/forumdisplay.php?f=79 - http://forum.springframework.org/forumdisplay.php?f=79 + https://forum.springframework.org/forumdisplay.php?f=79 + https://forum.springframework.org/forumdisplay.php?f=79 @@ -46,10 +52,10 @@ Apache 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt + https://www.apache.org/licenses/LICENSE-2.0.txt - + stoicflame @@ -88,24 +94,24 @@ bootstrap - spring-milestone + repo.spring.io-milestone Spring Framework Milestone Repository - http://s3.amazonaws.com/maven.springframework.org/milestone + https://repo.spring.io/libs-milestone-local - spring-release + repo.spring.io-release Spring Framework Release Repository - http://maven.springframework.org/release + https://repo.spring.io/libs-release-local - - repository.springframework.maven.snapshot - Spring Framework Maven Release Repository - http://maven.springframework.org/snapshot + repo.spring.io-snapshot + Spring Framework Maven Snapshot Repository + https://repo.spring.io/libs-snapshot-local + true oauth.googlecode.net - http://oauth.googlecode.com/svn/code/maven/ + https://oauth.googlecode.com/svn/code/maven/ @@ -113,12 +119,22 @@ milestone - spring-release - Spring Release Repository - s3://maven.springframework.org/release + repo.spring.io + Spring Milestone Repository + https://repo.spring.io/libs-milestone-local + + bintray + + + bintray + Jcenter Repository + https://api.bintray.com/maven/spring/jars/org.springframework.security.oauth + + + central @@ -150,8 +166,234 @@ + + spring5 + + 5.0.16.RELEASE + 5.0.3.RELEASE + 2.0.5.RELEASE + 2.9.0 + + + + repo.spring.io-milestone + Spring Framework Milestone Repository + https://repo.spring.io/libs-milestone-local + + + repo.spring.io-snapshot + Spring Framework Maven Snapshot Repository + https://repo.spring.io/libs-snapshot-local + true + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.1 + + + com.puppycrawl.tools + checkstyle + 8.31 + + + io.spring.nohttp + nohttp-checkstyle + 0.0.3.RELEASE + + + + ${maven.multiModuleProjectDirectory}/etc/nohttp/checkstyle.xml + src/**/*,* + + ./ + + + + + + check + + + + + + + + + default + + true + + + + + org.codehaus.mojo + animal-sniffer-maven-plugin + 1.6 + + + org.codehaus.mojo.signature + java16 + 1.0 + + + + + enforce-java-6 + test + + check + + + + + sun.net.www.protocol.http.* + sun.net.www.protocol.https.* + + + + + + + + + + tests-exclude-redis + + + + org.apache.maven.plugins + maven-surefire-plugin + + ${skipTests} + + **/*Tests.java + + + **/RedisTokenStorePrefixTests.java + **/RedisTokenStoreTests.java + + + + + + + + + + org.springframework + spring-beans + ${spring.version} + + + + org.springframework + spring-core + ${spring.version} + + + + org.springframework + spring-context + ${spring.version} + + + + org.springframework + spring-expression + ${spring.version} + + + + org.springframework + spring-aop + ${spring.version} + + + + org.springframework + spring-jdbc + ${spring.version} + true + + + + org.springframework + spring-tx + ${spring.version} + + + + org.springframework + spring-web + ${spring.version} + + + + org.springframework + spring-webmvc + ${spring.version} + + + + org.springframework + spring-test + ${spring.version} + test + + + + org.springframework.security + spring-security-core + ${spring.security.version} + + + + org.springframework.security + spring-security-config + ${spring.security.version} + + + + org.springframework.security + spring-security-jwt + ${spring.security.jwt.version} + true + + + + org.springframework.security + spring-security-web + ${spring.security.version} + + + org.springframework + spring-tx + + + + + + org.springframework.security + spring-security-taglibs + ${spring.security.version} + + + + commons-codec + commons-codec + ${commons-codec.version} + + + + + @@ -159,18 +401,8 @@ maven-compiler-plugin 2.3.2 - 1.5 - 1.5 - - - - org.apache.maven.plugins - maven-idea-plugin - 2.3-atlassian-1 - - true - true - target/tomcat,target/war + ${java.version} + ${java.version} @@ -188,39 +420,28 @@ - org.codehaus.mojo - animal-sniffer-maven-plugin - 1.6 + maven-javadoc-plugin + 2.9.1 - - org.codehaus.mojo.signature - java16 - 1.0 - + true - enforce-java-6 - test + javadoc + package - check + jar - - - - sun.net.www.protocol.http.* - sun.net.www.protocol.https.* - - - org.apache.maven.plugins maven-eclipse-plugin + 2.10 org.springframework.ide.eclipse.core.springnature @@ -234,70 +455,19 @@ - - com.springsource.bundlor - com.springsource.bundlor.maven - 1.0.0.RELEASE - - - bundlor - - bundlor - - - - - true - - - - maven-jar-plugin - - - target/classes/META-INF/MANIFEST.MF - - - - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - com.springsource.bundlor - com.springsource.bundlor.maven - [1.0,) - - bundlor - - - - - - - - - - + org.apache.tomcat.maven + tomcat7-maven-plugin + 2.2 + org.apache.maven.plugins maven-release-plugin - 2.3 + 2.5 org.apache.maven.plugins maven-site-plugin - 3.1 + 3.3 org.apache.maven.wagon @@ -305,14 +475,28 @@ 1.0 + + + org.apache.maven.plugins + maven-surefire-plugin + 2.20 + + ${skipTests} + + **/*Tests.java + + 3 + true + -Xmx1024m -XX:MaxPermSize=256m + - org.springframework.build.aws - org.springframework.build.aws.maven - 3.0.0.RELEASE + org.springframework.build + aws-maven + 5.0.0.RELEASE @@ -348,19 +532,19 @@ true true - http://java.sun.com/j2ee/1.4/docs/api - http://java.sun.com/j2se/1.5.0/docs/api - http://jakarta.apache.org/commons/collections/apidocs-COLLECTIONS_3_0/ - http://jakarta.apache.org/commons/dbcp/apidocs/ - http://jakarta.apache.org/commons/fileupload/apidocs/ - http://jakarta.apache.org/commons/httpclient/apidocs/ - http://jakarta.apache.org/commons/pool/apidocs/ - http://jakarta.apache.org/commons/logging/apidocs/ + https://java.sun.com/j2ee/1.4/docs/api + https://java.sun.com/j2se/1.5.0/docs/api + https://jakarta.apache.org/commons/collections/apidocs-COLLECTIONS_3_0/ + https://jakarta.apache.org/commons/dbcp/apidocs/ + https://jakarta.apache.org/commons/fileupload/apidocs/ + https://jakarta.apache.org/commons/httpclient/apidocs/ + https://jakarta.apache.org/commons/pool/apidocs/ + https://jakarta.apache.org/commons/logging/apidocs/ http://junit.sourceforge.net/javadoc/ - http://logging.apache.org/log4j/docs/api/ - http://jakarta.apache.org/regexp/apidocs/ - http://jakarta.apache.org/velocity/api/ - http://static.springframework.org/spring/docs/2.5.x/api/ + https://logging.apache.org/log4j/docs/api/ + https://jakarta.apache.org/regexp/apidocs/ + https://jakarta.apache.org/velocity/api/ + https://docs.spring.io/spring/docs/2.5.x/api/ example @@ -368,40 +552,28 @@ org.apache.maven.plugins maven-jxr-plugin + 2.4 - - - atlassian - https://maven.atlassian.com/repository/public - - - - repository.plugin.springsource.release - SpringSource Maven Repository - http://repository.springsource.com/maven/bundles/release - - - - static.springframework.org - scp://static.springframework.org/var/www/domains/springframework.org/static/htdocs/spring-security/oauth + static.spring.io + scp://docs-ip.spring.io/var/www/domains/spring.io/docs/htdocs/spring-security/oauth/site/docs/${project.version} - spring-release + repo.spring.io Spring Release Repository - s3://maven.springframework.org/release + https://repo.spring.io/libs-release-local - spring-snapshot + repo.spring.io Spring Snapshot Repository - s3://maven.springframework.org/snapshot + https://repo.spring.io/libs-snapshot-local diff --git a/samples/README.md b/samples/README.md old mode 100755 new mode 100644 index 992ff7aa7..9140c388e --- a/samples/README.md +++ b/samples/README.md @@ -1,3 +1,9 @@ +### Deprecation Notice + +The Spring Security OAuth project is deprecated. The latest OAuth 2.0 support is provided by Spring Security. See the [OAuth 2.0 Migration Guide](https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide) for further details. + +--- + These are the Spring Security OAuth sample apps and integration tests. They are split into OAuth (1a) and OAuth2 samples. Look in the subdirectory `oauth` and `oauth2` respectively for components of the @@ -19,7 +25,7 @@ to read his photos for the purpose of printing them. To run the apps the easiest thing is to first install all the artifacts using `mvn install` and then go to the `tonr` directory (in -`oauth` or `oauth2`) and run `mvn tomcat:run`. You can also use the +`oauth` or `oauth2`) and run `mvn tomcat7:run`. You can also use the command line to build war files with `mvn package` and drop them in your favourite server, or you can run them directly from an IDE. @@ -59,7 +65,7 @@ To deploy the apps in Eclipse you will need the Maven plugin (`m2e`) and the Web Tools Project (WTP) plugins. If you have SpringSource Toolsuite (STS) you should already have those, aso you can deploy the apps very simply. (Update the WTP plugin to at least version 0.12 at -http://m2eclipse.sonatype.org/sites/m2e-extras if you have an older +https://download.eclipse.org/technology/m2e/releases if you have an older one, or the context roots for the apps will be wrong.) * Ensure the Spring Security OAuth dependencies are available locally diff --git a/samples/oauth/sparklr/pom.xml b/samples/oauth/sparklr/pom.xml index 854d11474..7d4bca68c 100644 --- a/samples/oauth/sparklr/pom.xml +++ b/samples/oauth/sparklr/pom.xml @@ -1,11 +1,11 @@ - + 4.0.0 org.springframework.security.oauth spring-security-oauth-parent - 1.0.1.BUILD-SNAPSHOT + 2.5.3.BUILD-SNAPSHOT ../../.. @@ -32,8 +32,8 @@ - org.codehaus.mojo - tomcat-maven-plugin + org.apache.tomcat.maven + tomcat7-maven-plugin /sparklr true @@ -45,14 +45,9 @@ - spring-milestone - Spring Framework Milestone Repository - http://maven.springframework.org/milestone - - - spring-release - Spring Framework Release Repository - http://maven.springframework.org/release + spring + Spring Framework Repository + https://repo.spring.io/libs-snapshot @@ -101,7 +96,8 @@ - + diff --git a/samples/oauth/sparklr/src/main/java/org/springframework/security/oauth/examples/sparklr/impl/PhotoServiceImpl.java b/samples/oauth/sparklr/src/main/java/org/springframework/security/oauth/examples/sparklr/impl/PhotoServiceImpl.java index f803497ed..6b96f61e3 100644 --- a/samples/oauth/sparklr/src/main/java/org/springframework/security/oauth/examples/sparklr/impl/PhotoServiceImpl.java +++ b/samples/oauth/sparklr/src/main/java/org/springframework/security/oauth/examples/sparklr/impl/PhotoServiceImpl.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, diff --git a/samples/oauth/sparklr/src/main/java/org/springframework/security/oauth/examples/sparklr/oauth/SparklrConsumerDetails.java b/samples/oauth/sparklr/src/main/java/org/springframework/security/oauth/examples/sparklr/oauth/SparklrConsumerDetails.java index de896aa9b..6cc144fb1 100644 --- a/samples/oauth/sparklr/src/main/java/org/springframework/security/oauth/examples/sparklr/oauth/SparklrConsumerDetails.java +++ b/samples/oauth/sparklr/src/main/java/org/springframework/security/oauth/examples/sparklr/oauth/SparklrConsumerDetails.java @@ -5,6 +5,7 @@ /** * @author Ryan Heaton */ +@SuppressWarnings("serial") public class SparklrConsumerDetails extends BaseConsumerDetails { private String displayName; diff --git a/samples/oauth/sparklr/src/main/resources/simplelog.properties b/samples/oauth/sparklr/src/main/resources/simplelog.properties index dfba30062..cba65bf4c 100644 --- a/samples/oauth/sparklr/src/main/resources/simplelog.properties +++ b/samples/oauth/sparklr/src/main/resources/simplelog.properties @@ -1,2 +1,2 @@ -org.apache.commons.logging.simplelog.defaultlog=info -org.apache.commons.logging.simplelog.log.org.springframework.security=debug +org.apache.commons.logging.simplelog.defaultlog=warn +#org.apache.commons.logging.simplelog.log.org.springframework.security=debug diff --git a/samples/oauth/sparklr/src/main/webapp/LICENSE.txt b/samples/oauth/sparklr/src/main/webapp/LICENSE.txt deleted file mode 100644 index 5c83a364b..000000000 --- a/samples/oauth/sparklr/src/main/webapp/LICENSE.txt +++ /dev/null @@ -1,211 +0,0 @@ -THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE -("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE -OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. - -BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS -OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE -OF SUCH TERMS AND CONDITIONS. - -1. Definitions - - a. "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, - in which the Work in its entirety in unmodified form, along with a number of other - contributions, constituting separate and independent works in themselves, are assembled - into a collective whole. A work that constitutes a Collective Work will not be - considered a Derivative Work (as defined below) for the purposes of this License. - - b. "Derivative Work" means a work based upon the Work or upon the Work and other - pre-existing works, such as a translation, musical arrangement, dramatization, - fictionalization, motion picture version, sound recording, art reproduction, abridgment, - condensation, or any other form in which the Work may be recast, transformed, or adapted, - except that a work that constitutes a Collective Work will not be considered a Derivative - Work for the purpose of this License. For the avoidance of doubt, where the Work is a - musical composition or sound recording, the synchronization of the Work in timed-relation - with a moving image ("synching") will be considered a Derivative Work for the purpose of - this License. - - c. "Licensor" means the individual or entity that offers the Work under the terms of this - License. - - d. "Original Author" means the individual or entity who created the Work. - - e. "Work" means the copyrightable work of authorship offered under the terms of this - License. - - f. "You" means an individual or entity exercising rights under this License who has not - previously violated the terms of this License with respect to the Work, or who has - received express permission from the Licensor to exercise rights under this License - despite a previous violation. - - g. "License Elements" means the following high-level license attributes as selected by - Licensor and indicated in the title of this License: Attribution, ShareAlike. - -2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any rights -arising from fair use, first sale or other limitations on the exclusive rights of the copyright -owner under copyright law or other applicable laws. - -3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants -You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable -copyright) license to exercise the rights in the Work as stated below: - - a. to reproduce the Work, to incorporate the Work into one or more Collective Works, and to - reproduce the Work as incorporated in the Collective Works; - - b. to create and reproduce Derivative Works; - - c. to distribute copies or phonorecords of, display publicly, perform publicly, and perform - publicly by means of a digital audio transmission the Work including as incorporated in - Collective Works; - - d. to distribute copies or phonorecords of, display publicly, perform publicly, and perform - publicly by means of a digital audio transmission Derivative Works. - - e. For the avoidance of doubt, where the work is a musical composition: - - i. Performance Royalties Under Blanket Licenses. Licensor waives the exclusive right to - collect, whether individually or via a performance rights society (e.g. ASCAP, BMI, - SESAC), royalties for the public performance or public digital performance (e.g. - webcast) of the Work. - - ii. Mechanical Rights and Statutory Royalties. Licensor waives the exclusive right to - collect, whether individually or via a music rights society or designated agent (e.g. - Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover - version") and distribute, subject to the compulsory license created by 17 USC Section - 115 of the US Copyright Act (or the equivalent in other jurisdictions). - - f. Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is - a sound recording, Licensor waives the exclusive right to collect, whether individually - or via a performance-rights society (e.g. SoundExchange), royalties for the public - digital performance (e.g. webcast) of the Work, subject to the compulsory license created - by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions). - -The above rights may be exercised in all media and formats whether now known or hereafter devised. -The above rights include the right to make such modifications as are technically necessary to -exercise the rights in other media and formats. All rights not expressly granted by Licensor are -hereby reserved. - -4. Restrictions.The license granted in Section 3 above is expressly made subject to and limited by -the following restrictions: - - a. You may distribute, publicly display, publicly perform, or publicly digitally perform the - Work only under the terms of this License, and You must include a copy of, or the Uniform - Resource Identifier for, this License with every copy or phonorecord of the Work You - distribute, publicly display, publicly perform, or publicly digitally perform. You may - not offer or impose any terms on the Work that alter or restrict the terms of this - License or the recipients' exercise of the rights granted hereunder. You may not - sublicense the Work. You must keep intact all notices that refer to this License and to - the disclaimer of warranties. You may not distribute, publicly display, publicly perform, - or publicly digitally perform the Work with any technological measures that control - access or use of the Work in a manner inconsistent with the terms of this License - Agreement. The above applies to the Work as incorporated in a Collective Work, but this - does not require the Collective Work apart from the Work itself to be made subject to the - terms of this License. If You create a Collective Work, upon notice from any Licensor You - must, to the extent practicable, remove from the Collective Work any reference to such - Licensor or the Original Author, as requested. If You create a Derivative Work, upon - notice from any Licensor You must, to the extent practicable, remove from the Derivative - Work any reference to such Licensor or the Original Author, as requested. - - b. You may distribute, publicly display, publicly perform, or publicly digitally perform a - Derivative Work only under the terms of this License, a later version of this License - with the same License Elements as this License, or a Creative Commons iCommons license - that contains the same License Elements as this License (e.g. Attribution-ShareAlike 2.0 - Japan). You must include a copy of, or the Uniform Resource Identifier for, this License - or other license specified in the previous sentence with every copy or phonorecord of - each Derivative Work You distribute, publicly display, publicly perform, or publicly - digitally perform. You may not offer or impose any terms on the Derivative Works that - alter or restrict the terms of this License or the recipients' exercise of the rights - granted hereunder, and You must keep intact all notices that refer to this License and to - - the disclaimer of warranties. You may not distribute, publicly display, publicly perform, - or publicly digitally perform the Derivative Work with any technological measures that - control access or use of the Work in a manner inconsistent with the terms of this License - Agreement. The above applies to the Derivative Work as incorporated in a Collective Work, - but this does not require the Collective Work apart from the Derivative Work itself to be - made subject to the terms of this License. - - c. If you distribute, publicly display, publicly perform, or publicly digitally perform the - Work or any Derivative Works or Collective Works, You must keep intact all copyright - notices for the Work and give the Original Author credit reasonable to the medium or - means You are utilizing by conveying the name (or pseudonym if applicable) of the - Original Author if supplied; the title of the Work if supplied; to the extent reasonably - practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be - associated with the Work, unless such URI does not refer to the copyright notice or - licensing information for the Work; and in the case of a Derivative Work, a credit - identifying the use of the Work in the Derivative Work (e.g., "French translation of the - Work by Original Author," or "Screenplay based on original Work by Original Author"). - Such credit may be implemented in any reasonable manner; provided, however, that in the - case of a Derivative Work or Collective Work, at a minimum such credit will appear where - any other comparable authorship credit appears and in a manner at least as prominent as - such other comparable authorship credit. - -5. Representations, Warranties and Disclaimer - -UNLESS OTHERWISE AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO -REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE MATERIALS, EXPRESS, IMPLIED, STATUTORY OR -OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A -PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE -PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE -EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. - -6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL -LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE -OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN -ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. Termination - - a. This License and the rights granted hereunder will terminate automatically upon any - breach by You of the terms of this License. Individuals or entities who have received - Derivative Works or Collective Works from You under this License, however, will not have - their licenses terminated provided such individuals or entities remain in full compliance - with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this - License. - - b. Subject to the above terms and conditions, the license granted here is perpetual (for the - duration of the applicable copyright in the Work). Notwithstanding the above, Licensor - reserves the right to release the Work under different license terms or to stop - distributing the Work at any time; provided, however that any such election will not - serve to withdraw this License (or any other license that has been, or is required to be, - granted under the terms of this License), and this License will continue in full force - and effect unless terminated as stated above. - -8. Miscellaneous - - a. Each time You distribute or publicly digitally perform the Work or a Collective Work, the - Licensor offers to the recipient a license to the Work on the same terms and conditions - as the license granted to You under this License. - - b. Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers - to the recipient a license to the original Work on the same terms and conditions as the - license granted to You under this License. - - c. If any provision of this License is invalid or unenforceable under applicable law, it - shall not affect the validity or enforceability of the remainder of the terms of this - License, and without further action by the parties to this agreement, such provision - shall be reformed to the minimum extent necessary to make such provision valid and - enforceable. - - d. No term or provision of this License shall be deemed waived and no breach consented to - unless such waiver or consent shall be in writing and signed by the party to be charged - with such waiver or consent. - - e. This License constitutes the entire agreement between the parties with respect to the - Work licensed here. There are no understandings, agreements or representations with - respect to the Work not specified here. Licensor shall not be bound by any additional - provisions that may appear in any communication from You. This License may not be - modified without the mutual written agreement of the Licensor and You. - -Creative Commons is not a party to this License, and makes no warranty whatsoever in connection -with the Work. Creative Commons will not be liable to You or any party on any legal theory for -any damages whatsoever, including without limitation any general, special, incidental or -consequential damages arising in connection to this license. Notwithstanding the foregoing two -(2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, -it shall have all rights and obligations of Licensor. - -Except for the limited purpose of indicating to the public that the Work is licensed under the -CCPL, neither party will use the trademark "Creative Commons" or any related trademark or logo -of Creative Commons without the prior written consent of Creative Commons. Any permitted use will -be in compliance with Creative Commons' then-current trademark usage guidelines, as may be -published on its website or otherwise made available upon request from time to time. - -Creative Commons may be contacted at http://creativecommons.org/. \ No newline at end of file diff --git a/samples/oauth/sparklr/src/main/webapp/WEB-INF/applicationContext.xml b/samples/oauth/sparklr/src/main/webapp/WEB-INF/applicationContext.xml index 43349aa77..f33670c75 100644 --- a/samples/oauth/sparklr/src/main/webapp/WEB-INF/applicationContext.xml +++ b/samples/oauth/sparklr/src/main/webapp/WEB-INF/applicationContext.xml @@ -4,9 +4,9 @@ xmlns:beans="http://www.springframework.org/schema/beans" xmlns:oauth="http://www.springframework.org/schema/security/oauth" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://www.springframework.org/schema/beans%20http://www.springframework.org/schema/beans/spring-beans-3.0.xsd-%20%20%20%20%20%20%20%20%20%20%20%20%20%20http://www.springframework.org/schema/security%20http://www.springframework.org/schema/security/spring-security-3.1.xsd-%20%20%20%20%20%20%20%20%20%20%20%20%20%20http://www.springframework.org/schema/security/oauth%20http://www.springframework.org/schema/security/spring-security-oauth-1.0.xsd"> + xsi:schemaLocation="http://www.springframework.org/schema/beans%20https://www.springframework.org/schema/beans/spring-beans.xsd+%20%20%20%20%20%20%20%20%20%20%20%20%20%20http://www.springframework.org/schema/security%20https://www.springframework.org/schema/security/spring-security.xsd+%20%20%20%20%20%20%20%20%20%20%20%20%20%20http://www.springframework.org/schema/security/oauth%20https://www.springframework.org/schema/security/spring-security-oauth-1.0.xsd"> diff --git a/samples/oauth/sparklr/src/main/webapp/WEB-INF/jsp/access_confirmation.jsp b/samples/oauth/sparklr/src/main/webapp/WEB-INF/jsp/access_confirmation.jsp index f88681b83..0937858eb 100644 --- a/samples/oauth/sparklr/src/main/webapp/WEB-INF/jsp/access_confirmation.jsp +++ b/samples/oauth/sparklr/src/main/webapp/WEB-INF/jsp/access_confirmation.jsp @@ -1,8 +1,8 @@ <%@ page import="org.springframework.security.core.AuthenticationException" %> -<%@ page import="org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter" %> +<%@ page import="org.springframework.security.web.WebAttributes" %> <%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> - + @@ -20,12 +20,12 @@

Woops!

-

Access could not be granted. (<%= ((AuthenticationException) session.getAttribute(AbstractAuthenticationProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY)).getMessage() %>)

+

Access could not be granted. (<%= ((AuthenticationException) session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION)).getMessage() %>)

- +

Please Confirm

You hereby authorize "" to access the following resource:

@@ -44,7 +44,7 @@
- + diff --git a/samples/oauth/sparklr/src/main/webapp/WEB-INF/spring-servlet.xml b/samples/oauth/sparklr/src/main/webapp/WEB-INF/spring-servlet.xml index 65bcd5a97..b364e2142 100644 --- a/samples/oauth/sparklr/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/samples/oauth/sparklr/src/main/webapp/WEB-INF/spring-servlet.xml @@ -2,8 +2,8 @@ + xsi:schemaLocation="http://www.springframework.org/schema/mvc%20https://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd+http://www.springframework.org/schema/beans%20https://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> diff --git a/samples/oauth/sparklr/src/main/webapp/WEB-INF/web.xml b/samples/oauth/sparklr/src/main/webapp/WEB-INF/web.xml index 8492917b3..31390bf58 100644 --- a/samples/oauth/sparklr/src/main/webapp/WEB-INF/web.xml +++ b/samples/oauth/sparklr/src/main/webapp/WEB-INF/web.xml @@ -2,7 +2,7 @@ + "https://java.sun.com/dtd/web-app_2_3.dtd"> diff --git a/samples/oauth/sparklr/src/main/webapp/index.jsp b/samples/oauth/sparklr/src/main/webapp/index.jsp index 62ee6dff6..1e945d215 100644 --- a/samples/oauth/sparklr/src/main/webapp/index.jsp +++ b/samples/oauth/sparklr/src/main/webapp/index.jsp @@ -1,13 +1,13 @@ <%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> - + Sparklr "/> - + + - - - - - - - - - ]]> - - \ No newline at end of file diff --git a/samples/oauth2/sparklr/src/main/webapp/style.css b/samples/oauth2/sparklr/src/main/webapp/style.css deleted file mode 100644 index aab5b1e64..000000000 --- a/samples/oauth2/sparklr/src/main/webapp/style.css +++ /dev/null @@ -1,85 +0,0 @@ -/* CSS Document */ - -body { - color: white; - font-family: 'Trebuchet MS', Helvetica, sans-serif; - font-size: 12px; - margin: 0 auto; - width: 736px; - background: url(http://github.com/'images/bg.gif'); -} - -#content { - background: #3d3d3d; - width: 676px; - margin-top: 20px; - padding: 0 30px 25px 30px; -} - -a { - color: lightblue; - text-decoration: none; -} - -a:hover { - color: red; - text-decoration: none; -} - -h1 { - background: url(http://github.com/'images/header.jpg'); - height: 36px; - width: 721px; - margin: 0 0 1em 0; - padding-top: 80px; - padding-left: 15px; - font-size: 1.8em; - font-variant: small-caps; -} - -h2 { - font-size: 1.2em; - margin-left: -10px; - padding-top: 20px; - font-weight: bold; - letter-spacing: .3px; -} - -.error h2 { - color: red; - font-size: 1.2em; - padding-top: 20px; - font-weight: bold; - letter-spacing: .3px; -} - -.error p { - color: red; -} - -p { - letter-spacing: .2px; -} - -label { - text-indent: 20px; - letter-spacing: .2px; - padding: 5px 5px 5px 5px; -} - -#footer { - font-size: .8em; - margin-top: 1em; -} - -#footer a { - color: #333333; - font-weight: bold; - font-size: 1em -} - -#footer a:hover { - color: red; - font-weight: bold; - font-size: 1em -} \ No newline at end of file diff --git a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestAdminEndpoints.java b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/AdminEndpointsTests.java similarity index 63% rename from samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestAdminEndpoints.java rename to samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/AdminEndpointsTests.java index 8f22dd1ee..e2896c9fa 100644 --- a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestAdminEndpoints.java +++ b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/AdminEndpointsTests.java @@ -2,6 +2,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; import java.util.Arrays; @@ -18,11 +21,13 @@ import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException; +import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException; /** * @author Dave Syer */ -public class TestAdminEndpoints { +public class AdminEndpointsTests { @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); @@ -33,7 +38,7 @@ public class TestAdminEndpoints { @Test @OAuth2ContextConfiguration(ResourceOwnerReadOnly.class) public void testListTokensByUser() throws Exception { - ResponseEntity result = serverRunning.getForString("/sparklr2/oauth/users/marissa/tokens"); + ResponseEntity result = serverRunning.getForString("/sparklr2/oauth/clients/my-trusted-client/users/marissa/tokens"); assertEquals(HttpStatus.OK, result.getStatusCode()); // System.err.println(result.getBody()); assertTrue(result.getBody().contains(context.getAccessToken().getValue())); @@ -44,6 +49,7 @@ public void testListTokensByUser() throws Exception { public void testRevokeTokenByUser() throws Exception { OAuth2AccessToken token = context.getAccessToken(); + String tokenValueBeforeDeletion = token.getValue(); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); @@ -55,17 +61,27 @@ public void testRevokeTokenByUser() throws Exception { .getRestTemplate() .exchange(serverRunning.getUrl("/sparklr2/oauth/users/{user}/tokens/{token}"), HttpMethod.DELETE, request, Void.class, "marissa", token.getValue()).getStatusCode()); - - ResponseEntity result = serverRunning.getForString("/sparklr2/oauth/users/marissa/tokens", headers); - assertEquals(HttpStatus.UNAUTHORIZED, result.getStatusCode()); - assertTrue(result.getBody().contains("invalid_token")); - + try { + // The request above will delete the oauth token so that the next request will initially fail. However, + // the failure will be detected and a new access token will be obtained. The new access token + // only has "write" scope and the requested resource needs "read" scope. So, an insufficient_scope + // exception should be thrown. + ResponseEntity result = serverRunning.getForString("/sparklr2/oauth/clients/my-client-with-registered-redirect/users/marissa/tokens", headers); + fail("Should have thrown an exception"); + assertNotNull(result); + } catch (InsufficientScopeException ex) { + assertEquals(HttpStatus.FORBIDDEN.value(), ex.getHttpErrorCode()); + assertEquals("insufficient_scope", ex.getOAuth2ErrorCode()); + String secondTokenWithWriteOnlyScope = context.getOAuth2ClientContext().getAccessToken().getValue(); + assertNotNull(secondTokenWithWriteOnlyScope); + assertFalse(secondTokenWithWriteOnlyScope.equals(tokenValueBeforeDeletion)); + } } @Test @OAuth2ContextConfiguration(ClientCredentialsReadOnly.class) public void testClientListsTokensOfUser() throws Exception { - ResponseEntity result = serverRunning.getForString("/sparklr2/oauth/users/marissa/tokens"); + ResponseEntity result = serverRunning.getForString("/sparklr2/oauth/clients/my-client-with-registered-redirect/users/marissa/tokens"); assertEquals(HttpStatus.OK, result.getStatusCode()); assertTrue(result.getBody().startsWith("[")); assertTrue(result.getBody().endsWith("]")); @@ -75,7 +91,13 @@ public void testClientListsTokensOfUser() throws Exception { @Test @OAuth2ContextConfiguration(ResourceOwnerReadOnly.class) public void testCannotListTokensOfAnotherUser() throws Exception { - assertEquals(HttpStatus.FORBIDDEN, serverRunning.getStatusCode("/sparklr2/oauth/users/foo/tokens")); + try { + serverRunning.getStatusCode("/sparklr2/oauth/clients/my-client-with-registered-redirect/users/foo/tokens"); + fail("Should have thrown an exception"); + } catch (UserDeniedAuthorizationException ex) { + // assertEquals(HttpStatus.FORBIDDEN.value(), ex.getHttpErrorCode()); + assertEquals("access_denied", ex.getOAuth2ErrorCode()); + } } @Test @@ -90,8 +112,13 @@ public void testListTokensByClient() throws Exception { @Test @OAuth2ContextConfiguration(ResourceOwnerReadOnly.class) public void testUserCannotListTokensOfClient() throws Exception { - assertEquals(HttpStatus.FORBIDDEN, - serverRunning.getStatusCode("/sparklr2/oauth/clients/my-client-with-registered-redirect/tokens")); + try { + serverRunning.getStatusCode("/sparklr2/oauth/clients/my-client-with-registered-redirect/tokens"); + fail("Should have thrown an exception"); + } catch (UserDeniedAuthorizationException ex) { + // assertEquals(HttpStatus.FORBIDDEN.value(), ex.getHttpErrorCode()); + assertEquals("access_denied", ex.getOAuth2ErrorCode()); + } } static class ResourceOwnerReadOnly extends ResourceOwnerPasswordResourceDetails { @@ -101,7 +128,7 @@ public ResourceOwnerReadOnly(Object target) { setScope(Arrays.asList("read")); setUsername("marissa"); setPassword("koala"); - TestAdminEndpoints test = (TestAdminEndpoints) target; + AdminEndpointsTests test = (AdminEndpointsTests) target; setAccessTokenUri(test.serverRunning.getUrl("/sparklr2/oauth/token")); } } @@ -111,7 +138,7 @@ public ClientCredentialsReadOnly(Object target) { setClientId("my-client-with-registered-redirect"); setId(getClientId()); setScope(Arrays.asList("read")); - TestAdminEndpoints test = (TestAdminEndpoints) target; + AdminEndpointsTests test = (AdminEndpointsTests) target; setAccessTokenUri(test.serverRunning.getUrl("/sparklr2/oauth/token")); } } diff --git a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestAuthorizationCodeProvider.java b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/AuthorizationCodeProviderTests.java similarity index 86% rename from samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestAuthorizationCodeProvider.java rename to samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/AuthorizationCodeProviderTests.java index 446d33bfe..21bed14de 100755 --- a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestAuthorizationCodeProvider.java +++ b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/AuthorizationCodeProviderTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -21,6 +21,8 @@ import java.io.IOException; import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.junit.Rule; import org.junit.Test; @@ -38,7 +40,9 @@ import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException; import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException; +import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.ServerRunning.UriBuilder; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -50,7 +54,7 @@ * @author Dave Syer * @author Luke Taylor */ -public class TestAuthorizationCodeProvider { +public class AuthorizationCodeProviderTests { @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); @@ -60,8 +64,6 @@ public class TestAuthorizationCodeProvider { private AuthorizationCodeAccessTokenProvider accessTokenProvider; - private String cookie; - private ClientHttpResponse tokenEndpointResponse; @BeforeOAuth2Context @@ -116,24 +118,6 @@ public ResponseEntity extractData(ClientHttpResponse response) throws IOEx context.setAccessTokenProvider(accessTokenProvider); } - @BeforeOAuth2Context - public void loginAndExtractCookie() { - - MultiValueMap formData; - formData = new LinkedMultiValueMap(); - formData.add("j_username", "marissa"); - formData.add("j_password", "koala"); - - String location = "/sparklr2/login.do"; - ResponseEntity result = serverRunning.postForStatus(location, formData); - assertEquals(HttpStatus.FOUND, result.getStatusCode()); - String cookie = result.getHeaders().getFirst("Set-Cookie"); - - assertNotNull("Expected cookie in " + result.getHeaders(), cookie); - this.cookie = cookie; - - } - @Test public void testResourceIsProtected() throws Exception { // first make sure the resource is actually protected. @@ -145,8 +129,8 @@ public void testResourceIsProtected() throws Exception { public void testUnauthenticatedAuthorizationRequestRedirectsToLogin() throws Exception { AccessTokenRequest request = context.getAccessTokenRequest(); - request.setCurrentUri("http://anywhere/"); - request.add(AuthorizationRequest.USER_OAUTH_APPROVAL, "true"); + request.setCurrentUri("https://anywhere/"); + request.add(OAuth2Utils.USER_OAUTH_APPROVAL, "true"); String location = null; @@ -169,7 +153,7 @@ public void testUnauthenticatedAuthorizationRequestRedirectsToLogin() throws Exc public void testSuccessfulAuthorizationCodeFlow() throws Exception { // Once the request is ready and approved, we can continue with the access token - approveAccessTokenGrant("http://anywhere/", true); + approveAccessTokenGrant("https://anywhere/", true); // Finally everything is in place for the grant to happen... assertNotNull(context.getAccessToken()); @@ -183,10 +167,10 @@ public void testSuccessfulAuthorizationCodeFlow() throws Exception { @Test @OAuth2ContextConfiguration(resource = MyLessTrustedClient.class, initialize = false) public void testWrongRedirectUri() throws Exception { - approveAccessTokenGrant("http://anywhere/", true); + approveAccessTokenGrant("https://anywhere/", true); AccessTokenRequest request = context.getAccessTokenRequest(); // The redirect is stored in the preserved state... - context.getOAuth2ClientContext().setPreservedState(request.getStateKey(), "http://nowhere/"); + context.getOAuth2ClientContext().setPreservedState(request.getStateKey(), "https://nowhere/"); // Finally everything is in place for the grant to happen... try { assertNotNull(context.getAccessToken()); @@ -201,7 +185,7 @@ public void testWrongRedirectUri() throws Exception { @Test @OAuth2ContextConfiguration(resource = MyLessTrustedClient.class, initialize = false) public void testUserDeniesConfirmation() throws Exception { - approveAccessTokenGrant("http://anywhere/", false); + approveAccessTokenGrant("https://anywhere/", false); String location = null; try { assertNotNull(context.getAccessToken()); @@ -211,7 +195,7 @@ public void testUserDeniesConfirmation() throws Exception { location = e.getRedirectUri(); } assertTrue("Wrong location: " + location, location.contains("state=")); - assertTrue(location.startsWith("http://anywhere/")); + assertTrue(location.startsWith("https://anywhere/")); assertTrue(location.substring(location.indexOf('?')).contains("error=access_denied")); // It was a redirect that triggered our client redirect exception: assertEquals(HttpStatus.FOUND, tokenEndpointResponse.getStatusCode()); @@ -219,10 +203,9 @@ public void testUserDeniesConfirmation() throws Exception { @Test public void testNoClientIdProvided() throws Exception { - ResponseEntity response = attemptToGetConfirmationPage(null, "http://anywhere/"); + ResponseEntity response = attemptToGetConfirmationPage(null, "https://anywhere/"); // With no client id you get an InvalidClientException on the server which is forwarded to /oauth/error assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); - // TODO: assert the HTML content String body = response.getBody(); assertTrue("Wrong body: " + body, body.contains(" response = serverRunning.postForStatus(authorizeUrl, headers, new LinkedMultiValueMap()); @@ -283,7 +262,7 @@ public void testInvalidScopeInAuthorizationRequest() throws Exception { headers.set("Cookie", cookie); String scope = "bogus"; - String redirectUri = "http://anywhere/?key=value"; + String redirectUri = "https://anywhere/?key=value"; String clientId = "my-client-with-registered-redirect"; UriBuilder uri = serverRunning.buildUri("/sparklr2/oauth/authorize").queryParam("response_type", "code") @@ -297,7 +276,7 @@ public void testInvalidScopeInAuthorizationRequest() throws Exception { ResponseEntity response = serverRunning.getForString(uri.pattern(), headers, uri.params()); assertEquals(HttpStatus.FOUND, response.getStatusCode()); String location = response.getHeaders().getLocation().toString(); - assertTrue(location.startsWith("http://anywhere/")); + assertTrue(location.startsWith("https://anywhere/")); assertTrue(location.contains("error=invalid_scope")); assertFalse(location.contains("redirect_uri=")); } @@ -307,14 +286,15 @@ public void testInvalidScopeInAuthorizationRequest() throws Exception { public void testInsufficientScopeInResourceRequest() throws Exception { AuthorizationCodeResourceDetails resource = (AuthorizationCodeResourceDetails) context.getResource(); resource.setScope(Arrays.asList("trust")); - approveAccessTokenGrant("http://anywhere/?key=value", true); + approveAccessTokenGrant("https://anywhere/?key=value", true); assertNotNull(context.getAccessToken()); - ResponseEntity response = serverRunning.getForString("/sparklr2/photos?format=json"); - assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode()); - String authenticate = response.getHeaders().getFirst("WWW-Authenticate"); - assertNotNull(authenticate); - assertTrue(authenticate.startsWith("Bearer")); - assertTrue("Wrong header: " + authenticate, authenticate.contains("scope=\"")); + try { + serverRunning.getForString("/sparklr2/photos?format=json"); + fail("Should have thrown exception"); + } + catch (InsufficientScopeException ex) { + // ignore / all good + } } @Test @@ -338,7 +318,7 @@ public void testInvalidAccessToken() throws Exception { @OAuth2ContextConfiguration(resource = MyClientWithRegisteredRedirect.class, initialize = false) public void testRegisteredRedirectWithWrongRequestedRedirect() throws Exception { try { - approveAccessTokenGrant("http://nowhere/", true); + approveAccessTokenGrant("https://nowhere/", true); fail("Expected RedirectMismatchException"); } catch (RedirectMismatchException e) { @@ -349,9 +329,9 @@ public void testRegisteredRedirectWithWrongRequestedRedirect() throws Exception @Test @OAuth2ContextConfiguration(resource = MyClientWithRegisteredRedirect.class, initialize = false) public void testRegisteredRedirectWithWrongOneInTokenEndpoint() throws Exception { - approveAccessTokenGrant("http://anywhere/?key=value", true); + approveAccessTokenGrant("https://anywhere/?key=value", true); // Setting the redirect uri directly in the request shoiuld override the saved value - context.getAccessTokenRequest().set("redirect_uri", "http://nowhere.com/"); + context.getAccessTokenRequest().set("redirect_uri", "https://nowhere.com/"); try { assertNotNull(context.getAccessToken()); fail("Expected RedirectMismatchException"); @@ -363,10 +343,7 @@ public void testRegisteredRedirectWithWrongOneInTokenEndpoint() throws Exception private ResponseEntity attemptToGetConfirmationPage(String clientId, String redirectUri) { - if (cookie == null) { - cookie = loginAndGrabCookie(); - } - + String cookie = loginAndGrabCookie(); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); headers.set("Cookie", cookie); @@ -387,23 +364,31 @@ private String getAuthorizeUrl(String clientId, String redirectUri, String scope return uri.build().toString(); } - private String loginAndGrabCookie() { - HttpHeaders headers = new HttpHeaders(); - headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); + ResponseEntity page = serverRunning.getForString("/sparklr2/login.jsp"); + String cookie = page.getHeaders().getFirst("Set-Cookie"); + Matcher matcher = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*").matcher(page.getBody()); - MultiValueMap formData = new LinkedMultiValueMap(); - formData.add("j_username", "marissa"); - formData.add("j_password", "koala"); + MultiValueMap formData; + formData = new LinkedMultiValueMap(); + formData.add("username", "marissa"); + formData.add("password", "koala"); + if (matcher.matches()) { + formData.add("_csrf", matcher.group(1)); + } - // Should be redirected to the original URL, but now authenticated - ResponseEntity result = serverRunning.postForStatus("/sparklr2/login.do", headers, formData); + String location = "/sparklr2/login"; + HttpHeaders headers = new HttpHeaders(); + headers.set("Cookie", cookie); + headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); + ResponseEntity result = serverRunning.postForStatus(location, headers , formData); assertEquals(HttpStatus.FOUND, result.getStatusCode()); + cookie = result.getHeaders().getFirst("Set-Cookie"); - assertTrue(result.getHeaders().containsKey("Set-Cookie")); + assertNotNull("Expected cookie in " + result.getHeaders(), cookie); - return result.getHeaders().getFirst("Set-Cookie"); + return cookie; } @@ -412,6 +397,7 @@ private void approveAccessTokenGrant(String currentUri, boolean approved) { AccessTokenRequest request = context.getAccessTokenRequest(); AuthorizationCodeResourceDetails resource = (AuthorizationCodeResourceDetails) context.getResource(); + String cookie = loginAndGrabCookie(); request.setCookie(cookie); if (currentUri != null) { request.setCurrentUri(currentUri); @@ -446,7 +432,7 @@ private void approveAccessTokenGrant(String currentUri, boolean approved) { assertNull(request.getAuthorizationCode()); // The approval (will be processed on the next attempt to obtain an access token)... - request.set(AuthorizationRequest.USER_OAUTH_APPROVAL, "" + approved); + request.set(OAuth2Utils.USER_OAUTH_APPROVAL, "" + approved); } @@ -456,7 +442,7 @@ public MyLessTrustedClient(Object target) { setClientId("my-less-trusted-client"); setScope(Arrays.asList("read")); setId(getClientId()); - TestAuthorizationCodeProvider test = (TestAuthorizationCodeProvider) target; + AuthorizationCodeProviderTests test = (AuthorizationCodeProviderTests) target; setAccessTokenUri(test.serverRunning.getUrl("/sparklr2/oauth/token")); setUserAuthorizationUri(test.serverRunning.getUrl("/sparklr2/oauth/authorize")); } @@ -466,7 +452,7 @@ static class MyClientWithRegisteredRedirect extends MyLessTrustedClient { public MyClientWithRegisteredRedirect(Object target) { super(target); setClientId("my-client-with-registered-redirect"); - setPreEstablishedRedirectUri("http://anywhere/?key=value"); + setPreEstablishedRedirectUri("https://anywhere/?key=value"); } } } diff --git a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestClientCredentialsProvider.java b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/ClientCredentialsProviderTests.java similarity index 64% rename from samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestClientCredentialsProvider.java rename to samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/ClientCredentialsProviderTests.java index daa0205aa..d99218e9a 100644 --- a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestClientCredentialsProvider.java +++ b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/ClientCredentialsProviderTests.java @@ -18,7 +18,6 @@ import org.springframework.security.oauth2.client.test.OAuth2ContextSetup; import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider; import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; -import org.springframework.security.oauth2.common.AuthenticationScheme; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.ResponseErrorHandler; @@ -27,7 +26,7 @@ * @author Ryan Heaton * @author Dave Syer */ -public class TestClientCredentialsProvider { +public class ClientCredentialsProviderTests { @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); @@ -35,8 +34,6 @@ public class TestClientCredentialsProvider { @Rule public OAuth2ContextSetup context = OAuth2ContextSetup.standard(serverRunning); - private ClientCredentialsResourceDetails resource; - private HttpHeaders responseHeaders; private HttpStatus responseStatus; @@ -51,16 +48,6 @@ public void testPostForToken() throws Exception { assertNull(token.getRefreshToken()); } - /** - * tests the basic provider - */ - @Test - @OAuth2ContextConfiguration(FormClientCredentials.class) - public void testPostForTokenWithForm() throws Exception { - OAuth2AccessToken token = context.getAccessToken(); - assertNull(token.getRefreshToken()); - } - /** * tests that the registered scopes are used as defaults */ @@ -68,7 +55,7 @@ public void testPostForTokenWithForm() throws Exception { @OAuth2ContextConfiguration(NoScopeClientCredentials.class) public void testPostForTokenWithNoScopes() throws Exception { OAuth2AccessToken token = context.getAccessToken(); - assertFalse(token.getScope().isEmpty()); + assertFalse("Wrong scope: " + token.getScope(), token.getScope().isEmpty()); } @Test @@ -94,35 +81,7 @@ public void handleError(ClientHttpResponse response) throws IOException { } // System.err.println(responseHeaders); String header = responseHeaders.getFirst("WWW-Authenticate"); - assertTrue("Wrong header: " + header, header.contains("error=\"invalid_client\"")); - assertEquals(HttpStatus.UNAUTHORIZED, responseStatus); - } - - @Test - @OAuth2ContextConfiguration(resource = InvalidClientCredentials.class, initialize = false) - public void testInvalidCredentialsWithFormAuthentication() throws Exception { - resource.setAuthenticationScheme(AuthenticationScheme.form); - context.setAccessTokenProvider(new ClientCredentialsAccessTokenProvider() { - @Override - protected ResponseErrorHandler getResponseErrorHandler() { - return new DefaultResponseErrorHandler() { - public void handleError(ClientHttpResponse response) throws IOException { - responseHeaders = response.getHeaders(); - responseStatus = response.getStatusCode(); - } - }; - } - }); - try { - context.getAccessToken(); - fail("Expected ResourceAccessException"); - } - catch (Exception e) { - // ignore - } - // System.err.println(responseHeaders); - String header = responseHeaders.getFirst("WWW-Authenticate"); - assertTrue("Wrong header: " + header, header.contains("error=\"invalid_client\"")); + assertTrue("Wrong header: " + header, header.contains("Basic realm")); assertEquals(HttpStatus.UNAUTHORIZED, responseStatus); } @@ -131,16 +90,8 @@ public ClientCredentials(Object target) { setClientId("my-client-with-registered-redirect"); setScope(Arrays.asList("read")); setId(getClientId()); - TestClientCredentialsProvider test = (TestClientCredentialsProvider) target; + ClientCredentialsProviderTests test = (ClientCredentialsProviderTests) target; setAccessTokenUri(test.serverRunning.getUrl("/sparklr2/oauth/token")); - test.resource = this; - } - } - - static class FormClientCredentials extends ClientCredentials { - public FormClientCredentials(Object target) { - super(target); - setClientAuthenticationScheme(AuthenticationScheme.form); } } @@ -148,7 +99,7 @@ static class NoScopeClientCredentials extends ClientCredentialsResourceDetails { public NoScopeClientCredentials(Object target) { setClientId("my-client-with-registered-redirect"); setId(getClientId()); - TestClientCredentialsProvider test = (TestClientCredentialsProvider) target; + ClientCredentialsProviderTests test = (ClientCredentialsProviderTests) target; setAccessTokenUri(test.serverRunning.getUrl("/sparklr2/oauth/token")); } } diff --git a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestImplicitProvider.java b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/ImplicitProviderTests.java similarity index 51% rename from samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestImplicitProvider.java rename to samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/ImplicitProviderTests.java index 75f7a0cbc..37478f706 100644 --- a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestImplicitProvider.java +++ b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/ImplicitProviderTests.java @@ -1,28 +1,35 @@ package org.springframework.security.oauth2.provider; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; - -import java.util.Arrays; - import org.junit.Rule; import org.junit.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; -import org.springframework.security.oauth2.client.test.BeforeOAuth2Context; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; import org.springframework.security.oauth2.client.test.OAuth2ContextSetup; +import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitAccessTokenProvider; import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitResourceDetails; +import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import java.io.IOException; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.junit.Assert.*; + /** * @author Ryan Heaton * @author Dave Syer */ -public class TestImplicitProvider { +public class ImplicitProviderTests { @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); @@ -30,23 +37,31 @@ public class TestImplicitProvider { @Rule public OAuth2ContextSetup context = OAuth2ContextSetup.standard(serverRunning); - private String cookie; + private HttpHeaders latestHeaders = null; - @BeforeOAuth2Context - public void loginAndExtractCookie() { + private String loginAndExtractCookie() { + + ResponseEntity page = serverRunning.getForString("/sparklr2/login.jsp"); + String cookie = page.getHeaders().getFirst("Set-Cookie"); + HttpHeaders headers = new HttpHeaders(); + headers.set("Cookie", cookie); + Matcher matcher = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*").matcher(page.getBody()); MultiValueMap formData; formData = new LinkedMultiValueMap(); - formData.add("j_username", "marissa"); - formData.add("j_password", "koala"); + formData.add("username", "marissa"); + formData.add("password", "koala"); + if (matcher.matches()) { + formData.add("_csrf", matcher.group(1)); + } - String location = "/sparklr2/login.do"; - ResponseEntity result = serverRunning.postForStatus(location, formData); + String location = "/sparklr2/login"; + ResponseEntity result = serverRunning.postForStatus(location, headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode()); - String cookie = result.getHeaders().getFirst("Set-Cookie"); + cookie = result.getHeaders().getFirst("Set-Cookie"); assertNotNull("Expected cookie in " + result.getHeaders(), cookie); - this.cookie = cookie; + return cookie; } @@ -59,14 +74,27 @@ public void testRedirectRequiredForAuthentication() throws Exception { @Test @OAuth2ContextConfiguration(resource = AutoApproveImplicit.class, initialize = false) public void testPostForAutomaticApprovalToken() throws Exception { - context.getAccessTokenRequest().setCookie(cookie); + final ImplicitAccessTokenProvider implicitProvider = new ImplicitAccessTokenProvider(); + implicitProvider.setInterceptors(Arrays + . asList(new ClientHttpRequestInterceptor() { + public ClientHttpResponse intercept(HttpRequest request, byte[] body, + ClientHttpRequestExecution execution) throws IOException { + ClientHttpResponse result = execution.execute(request, body); + latestHeaders = result.getHeaders(); + return result; + } + })); + context.setAccessTokenProvider(implicitProvider); + context.getAccessTokenRequest().setCookie(this.loginAndExtractCookie()); assertNotNull(context.getAccessToken()); + assertTrue("Wrong location header: " + latestHeaders.getLocation().getFragment(), latestHeaders.getLocation().getFragment() + .contains("scope=read write trust")); } @Test @OAuth2ContextConfiguration(resource = NonAutoApproveImplicit.class, initialize = false) public void testPostForNonAutomaticApprovalToken() throws Exception { - context.getAccessTokenRequest().setCookie(cookie); + context.getAccessTokenRequest().setCookie(this.loginAndExtractCookie()); try { assertNotNull(context.getAccessToken()); fail("Expected UserRedirectRequiredException"); @@ -75,7 +103,8 @@ public void testPostForNonAutomaticApprovalToken() throws Exception { // ignore } // add user approval parameter for the second request - context.getAccessTokenRequest().add(AuthorizationRequest.USER_OAUTH_APPROVAL, "true"); + context.getAccessTokenRequest().add(OAuth2Utils.USER_OAUTH_APPROVAL, "true"); + context.getAccessTokenRequest().add("scope.read", "true"); assertNotNull(context.getAccessToken()); } @@ -83,10 +112,9 @@ static class AutoApproveImplicit extends ImplicitResourceDetails { public AutoApproveImplicit(Object target) { super(); setClientId("my-less-trusted-autoapprove-client"); - setScope(Arrays.asList("read")); setId(getClientId()); - setPreEstablishedRedirectUri("http://anywhere/"); - TestImplicitProvider test = (TestImplicitProvider) target; + setPreEstablishedRedirectUri("https://anywhere/"); + ImplicitProviderTests test = (ImplicitProviderTests) target; setAccessTokenUri(test.serverRunning.getUrl("/sparklr2/oauth/authorize")); setUserAuthorizationUri(test.serverRunning.getUrl("/sparklr2/oauth/authorize")); } diff --git a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestRefreshTokenSupport.java b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/RefreshTokenSupportTests.java similarity index 78% rename from samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestRefreshTokenSupport.java rename to samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/RefreshTokenSupportTests.java index 77eb6717b..bd63a1994 100644 --- a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestRefreshTokenSupport.java +++ b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/RefreshTokenSupportTests.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertNotNull; import java.util.Map; @@ -11,6 +12,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.crypto.codec.Base64; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.util.LinkedMultiValueMap; @@ -20,7 +22,7 @@ * @author Ryan Heaton * @author Dave Syer */ -public class TestRefreshTokenSupport { +public class RefreshTokenSupportTests { @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); @@ -57,11 +59,12 @@ private OAuth2AccessToken refreshAccessToken(String refreshToken) { formData.add("grant_type", "refresh_token"); formData.add("client_id", "my-trusted-client"); formData.add("refresh_token", refreshToken); + HttpHeaders headers = getTokenHeaders("my-trusted-client"); @SuppressWarnings("rawtypes") - ResponseEntity response = serverRunning.postForMap("/sparklr2/oauth/token", formData); + ResponseEntity response = serverRunning.postForMap("/sparklr2/oauth/token", headers, formData); assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals("no-store", response.getHeaders().getFirst("Cache-Control")); + assertTrue("Wrong cache control: " + response.getHeaders().getFirst("Cache-Control"), response.getHeaders().getFirst("Cache-Control").contains("no-store")); @SuppressWarnings("unchecked") OAuth2AccessToken newAccessToken = DefaultOAuth2AccessToken.valueOf(response.getBody()); return newAccessToken; @@ -70,17 +73,26 @@ private OAuth2AccessToken refreshAccessToken(String refreshToken) { private OAuth2AccessToken getAccessToken(String scope, String clientId) throws Exception { MultiValueMap formData = getTokenFormData(scope, clientId); + HttpHeaders headers = getTokenHeaders(clientId); @SuppressWarnings("rawtypes") - ResponseEntity response = serverRunning.postForMap("/sparklr2/oauth/token", formData); + ResponseEntity response = serverRunning.postForMap("/sparklr2/oauth/token", headers, formData); assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals("no-store", response.getHeaders().getFirst("Cache-Control")); + assertTrue("Wrong cache control: " + response.getHeaders().getFirst("Cache-Control"), response.getHeaders().getFirst("Cache-Control").contains("no-store")); @SuppressWarnings("unchecked") OAuth2AccessToken accessToken = DefaultOAuth2AccessToken.valueOf(response.getBody()); return accessToken; } + private HttpHeaders getTokenHeaders(String clientId) { + HttpHeaders headers = new HttpHeaders(); + if (clientId != null) { + headers.set("Authorization", "Basic " + new String(Base64.encode((clientId + ":").getBytes()))); + } + return headers ; + } + private MultiValueMap getTokenFormData(String scope, String clientId) { MultiValueMap formData = new LinkedMultiValueMap(); formData.add("grant_type", "password"); diff --git a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestResourceOwnerPasswordProvider.java b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/ResourceOwnerPasswordProviderTests.java similarity index 94% rename from samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestResourceOwnerPasswordProvider.java rename to samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/ResourceOwnerPasswordProviderTests.java index be0cffb96..b62a28f8a 100644 --- a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestResourceOwnerPasswordProvider.java +++ b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/ResourceOwnerPasswordProviderTests.java @@ -24,7 +24,7 @@ import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; import org.springframework.security.oauth2.common.AuthenticationScheme; import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.util.LinkedMultiValueMap; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.ResponseErrorHandler; @@ -34,7 +34,7 @@ * @author Ryan Heaton * @author Dave Syer */ -public class TestResourceOwnerPasswordProvider { +public class ResourceOwnerPasswordProviderTests { @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); @@ -132,7 +132,8 @@ public void testTokenNotGrantedIfSecretNotProvided() throws Exception { assertEquals(HttpStatus.UNAUTHORIZED, e.getStatusCode()); List values = tokenEndpointResponse.getHeaders().get("WWW-Authenticate"); assertEquals(1, values.size()); - assertEquals("Basic realm=\"sparklr2/client\"", values.get(0)); + String header = values.get(0); + assertTrue("Wrong header " + header, header.contains("Basic realm=\"sparklr2/client\"")); } } @@ -151,16 +152,16 @@ public void testSecretProvidedInHeader() throws Exception { @Test @OAuth2ContextConfiguration(resource = InvalidGrantType.class, initialize = false) public void testInvalidGrantType() throws Exception { - + // The error comes back as additional information because OAuth2AccessToken is so extensible! try { context.getAccessToken(); } - catch (OAuth2Exception e) { - assertEquals("invalid_grant", e.getOAuth2ErrorCode()); + catch (Exception e) { + // assertEquals("invalid_client", e.getOAuth2ErrorCode()); } - assertEquals(HttpStatus.BAD_REQUEST, tokenEndpointResponse.getStatusCode()); + assertEquals(HttpStatus.UNAUTHORIZED, tokenEndpointResponse.getStatusCode()); List newCookies = tokenEndpointResponse.getHeaders().get("Set-Cookie"); if (newCookies != null && !newCookies.isEmpty()) { @@ -213,7 +214,7 @@ public void testMissingGrantType() throws Exception { HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", String.format("Basic %s", new String(Base64.encode("my-trusted-client:".getBytes())))); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); - ResponseEntity response = serverRunning.getForString("/sparklr2/oauth/token", headers); + ResponseEntity response = serverRunning.postForString("/sparklr2/oauth/token", headers, new LinkedMultiValueMap()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertTrue(response.getBody().contains("invalid_request")); } @@ -225,7 +226,7 @@ public ResourceOwner(Object target) { setId(getClientId()); setUsername("marissa"); setPassword("koala"); - TestResourceOwnerPasswordProvider test = (TestResourceOwnerPasswordProvider) target; + ResourceOwnerPasswordProviderTests test = (ResourceOwnerPasswordProviderTests) target; setAccessTokenUri(test.serverRunning.getUrl("/sparklr2/oauth/token")); } } diff --git a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/ServerRunning.java b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/ServerRunning.java index 70a859102..2c21e3397 100644 --- a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/ServerRunning.java +++ b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/ServerRunning.java @@ -11,9 +11,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.http.client.HttpClient; -import org.apache.http.client.params.ClientPNames; -import org.apache.http.client.params.CookiePolicy; +import org.apache.http.client.config.CookieSpecs; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.config.RequestConfig.Builder; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.protocol.HttpContext; import org.junit.Assume; import org.junit.internal.AssumptionViolatedException; import org.junit.rules.MethodRule; @@ -30,7 +32,7 @@ import org.springframework.security.oauth2.client.test.RestTemplateHolder; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; @@ -163,8 +165,7 @@ public Statement apply(final Statement base, FrameworkMethod method, Object targ } final RestOperations savedClient = getRestTemplate(); - postForStatus(savedClient, "/sparklr2/oauth/uncache_approvals", - new LinkedMultiValueMap()); + postForStatus(savedClient, "/sparklr2/oauth/uncache_approvals", new LinkedMultiValueMap()); return new Statement() { @@ -207,7 +208,7 @@ public ResponseEntity postForString(String path, MultiValueMap postForString(String path, HttpHeaders headers, MultiValueMap formData) { HttpHeaders actualHeaders = new HttpHeaders(); actualHeaders.putAll(headers); - actualHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED)); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); return client.exchange(getUrl(path), HttpMethod.POST, new HttpEntity>(formData, actualHeaders), String.class); } @@ -245,7 +246,7 @@ private ResponseEntity postForStatus(RestOperations client, String path, H actualHeaders.putAll(headers); actualHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); return client.exchange(getUrl(path), HttpMethod.POST, new HttpEntity>(formData, - actualHeaders), null); + actualHeaders), (Class) null); } public ResponseEntity postForRedirect(String path, HttpHeaders headers, MultiValueMap params) { @@ -262,7 +263,7 @@ public ResponseEntity postForRedirect(String path, HttpHeaders headers, Mu String location = exchange.getHeaders().getLocation().toString(); - return client.exchange(location, HttpMethod.GET, new HttpEntity(null, headers), null); + return client.exchange(location, HttpMethod.GET, new HttpEntity(null, headers), (Class) null); } public ResponseEntity getForString(String path) { @@ -280,7 +281,7 @@ public ResponseEntity getForString(String path, final HttpHeaders header public ResponseEntity getForResponse(String path, final HttpHeaders headers, Map uriVariables) { HttpEntity request = new HttpEntity(null, headers); - return client.exchange(getUrl(path), HttpMethod.GET, request, null, uriVariables); + return client.exchange(getUrl(path), HttpMethod.GET, request, (Class) null, uriVariables); } public ResponseEntity getForResponse(String path, HttpHeaders headers) { @@ -303,26 +304,24 @@ public void setRestTemplate(RestOperations restTemplate) { public RestOperations getRestTemplate() { return client; } - + public RestOperations createRestTemplate() { RestTemplate client = new RestTemplate(); client.setRequestFactory(new HttpComponentsClientHttpRequestFactory() { @Override - public HttpClient getHttpClient() { - HttpClient client = super.getHttpClient(); - client.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false); - client.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES); - return client; + protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) { + HttpClientContext context = HttpClientContext.create(); + Builder builder = RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES) + .setAuthenticationEnabled(false).setRedirectsEnabled(false); + context.setRequestConfig(builder.build()); + return context; } }); - client.setErrorHandler(new ResponseErrorHandler() { - // Pass errors through in response entity for status code analysis + client.setErrorHandler(new DefaultResponseErrorHandler() { + @Override public boolean hasError(ClientHttpResponse response) throws IOException { return false; } - - public void handleError(ClientHttpResponse response) throws IOException { - } }); return client; } diff --git a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestBootstrap.java b/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestBootstrap.java deleted file mode 100755 index 792f917a9..000000000 --- a/samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestBootstrap.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2006-2010 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package org.springframework.security.oauth2.provider; - -import org.junit.Test; -import org.springframework.context.support.GenericXmlApplicationContext; -import org.springframework.core.io.FileSystemResource; - -/** - * @author Dave Syer - * - */ -public class TestBootstrap { - - @Test - public void testRootContext() throws Exception { - GenericXmlApplicationContext context = new GenericXmlApplicationContext(new FileSystemResource("src/main/webapp/WEB-INF/spring-servlet.xml")); - context.close(); - } - -} diff --git a/samples/oauth2/sparklr/src/test/java/org/springframework/security/samples/config/ApplicationConfigurationTests.java b/samples/oauth2/sparklr/src/test/java/org/springframework/security/samples/config/ApplicationConfigurationTests.java new file mode 100644 index 000000000..fe6d2ee91 --- /dev/null +++ b/samples/oauth2/sparklr/src/test/java/org/springframework/security/samples/config/ApplicationConfigurationTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.samples.config; + +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth.examples.sparklr.config.SecurityConfiguration; +import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +/** + * @author Rob Winch + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@WebAppConfiguration +public class ApplicationConfigurationTests { + + @Configuration + @ComponentScan(basePackageClasses = SecurityConfiguration.class) + public static class Config {} + + @Autowired + private FilterChainProxy springSecurityFilterChain; + + @Autowired + private AuthorizationEndpoint endpoint; + + @Test + public void securityConfigurationLoads() { + assertNotNull(endpoint); + } +} diff --git a/samples/oauth2/tonr/.springBeans b/samples/oauth2/tonr/.springBeans index 0952433a9..f25fc5ab4 100644 --- a/samples/oauth2/tonr/.springBeans +++ b/samples/oauth2/tonr/.springBeans @@ -1,13 +1,12 @@ 1 - + - src/main/webapp/WEB-INF/spring-servlet.xml diff --git a/samples/oauth2/tonr/pom.xml b/samples/oauth2/tonr/pom.xml index 63c7ff191..43bdf16e4 100644 --- a/samples/oauth2/tonr/pom.xml +++ b/samples/oauth2/tonr/pom.xml @@ -1,11 +1,12 @@ - + 4.0.0 org.springframework.security.oauth spring-security-oauth-parent - 1.0.1.BUILD-SNAPSHOT + 2.5.3.BUILD-SNAPSHOT ../../.. @@ -13,7 +14,20 @@ war OAuth for Spring Security - Tonr2 (OAuth 2 Client Example) + + /tonr2 + 2.10.5.1 + 3.0.1 + + + + spring5 + + 2.10.5.1 + 3.1.0 + + integration @@ -37,9 +51,8 @@ - org.codehaus.mojo - tomcat-maven-plugin - 1.1 + org.apache.tomcat.maven + tomcat7-maven-plugin start-tomcat @@ -48,6 +61,7 @@ run + /tonr2 true @@ -72,10 +86,29 @@ - org.codehaus.mojo - tomcat-maven-plugin + org.apache.tomcat.maven + tomcat7-maven-plugin true + + + /sparklr2 + ${project.groupId} + sparklr2 + ${project.version} + war + true + + + + + + org.apache.maven.plugins + maven-war-plugin + 2.3 + + + false @@ -84,27 +117,14 @@ - spring-milestone - Spring Framework Milestone Repository - http://maven.springframework.org/milestone - - - spring-release - Spring Framework Release Repository - http://maven.springframework.org/release + spring + Spring Framework Repository + https://repo.spring.io/libs-snapshot - - ${pom.groupId} - sparklr2 - ${pom.version} - war - tomcat - - ${project.groupId} spring-security-oauth2 @@ -114,61 +134,59 @@ org.springframework.security spring-security-taglibs - ${spring.security.version} org.springframework - spring-webmvc - ${spring.version} + spring-web org.springframework - spring-web - ${spring.version} + spring-webmvc org.springframework spring-jdbc - ${spring.version} org.springframework spring-context - ${spring.version} org.springframework spring-aop - ${spring.version} org.springframework spring-expression - ${spring.version} org.springframework spring-tx - ${spring.version} - org.codehaus.jackson - jackson-mapper-asl - 1.5.5 + org.springframework + spring-test + test + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson2.version} javax.servlet - servlet-api - 2.3 + javax.servlet-api + ${servlet-api.version} provided @@ -178,10 +196,16 @@ 1.2 + + org.webjars + bootstrap + 3.0.3 + + junit junit - 4.8.2 + ${junit.version} test diff --git a/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/config/SecurityConfig.java b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/config/SecurityConfig.java new file mode 100644 index 000000000..4ab10a75f --- /dev/null +++ b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/config/SecurityConfig.java @@ -0,0 +1,44 @@ +package org.springframework.security.oauth.examples.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication().withUser("marissa").password("wombat").roles("USER").and().withUser("sam") + .password("kangaroo").roles("USER"); + } + + @Override + public void configure(WebSecurity web) throws Exception { + web.ignoring().antMatchers("/resources/**"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http.authorizeRequests() + .antMatchers("/sparklr/**","/facebook/**").hasRole("USER") + .anyRequest().permitAll() + .and() + .logout() + .logoutSuccessUrl("/login.jsp") + .permitAll() + .and() + .formLogin() + .loginProcessingUrl("/login") + .loginPage("/login.jsp") + .failureUrl("/login.jsp?authentication_error=true") + .permitAll(); + // @formatter:on + } + +} diff --git a/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/config/ServletInitializer.java b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/config/ServletInitializer.java new file mode 100644 index 000000000..73b2be0ba --- /dev/null +++ b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/config/ServletInitializer.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013-2014 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.oauth.examples.config; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.filter.DelegatingFilterProxy; +import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer; + +/** + * @author Dave Syer + * + */ +public class ServletInitializer extends AbstractDispatcherServletInitializer { + + @Override + protected WebApplicationContext createServletApplicationContext() { + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.register(SecurityConfig.class, WebMvcConfig.class); + return context; + } + + @Override + protected String[] getServletMappings() { + return new String[] { "/" }; + } + + @Override + protected WebApplicationContext createRootApplicationContext() { + return null; + } + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + super.onStartup(servletContext); + registerProxyFilter(servletContext, "springSecurityFilterChain"); + registerProxyFilter(servletContext, "oauth2ClientContextFilter"); + } + + private void registerProxyFilter(ServletContext servletContext, String name) { + DelegatingFilterProxy filter = new DelegatingFilterProxy(name); + filter.setContextAttribute("org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher"); + servletContext.addFilter(name, filter).addMappingForUrlPatterns(null, false, "/*"); + } + +} diff --git a/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/config/WebMvcConfig.java b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/config/WebMvcConfig.java new file mode 100644 index 000000000..a6103958c --- /dev/null +++ b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/config/WebMvcConfig.java @@ -0,0 +1,235 @@ +package org.springframework.security.oauth.examples.config; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.context.support.ConversionServiceFactoryBean; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.http.MediaType; +import org.springframework.http.converter.BufferedImageHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.oauth.examples.tonr.SparklrService; +import org.springframework.security.oauth.examples.tonr.converter.AccessTokenRequestConverter; +import org.springframework.security.oauth.examples.tonr.impl.SparklrServiceImpl; +import org.springframework.security.oauth.examples.tonr.mvc.FacebookController; +import org.springframework.security.oauth.examples.tonr.mvc.SparklrController; +import org.springframework.security.oauth.examples.tonr.mvc.SparklrRedirectController; +import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; +import org.springframework.security.oauth2.client.OAuth2ClientContext; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; +import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; +import org.springframework.security.oauth2.common.AuthenticationScheme; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; +import org.springframework.web.accept.ContentNegotiationManagerFactoryBean; +import org.springframework.web.client.RestOperations; +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.ViewResolver; +import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; +import org.springframework.web.servlet.view.InternalResourceViewResolver; +import org.springframework.web.servlet.view.json.MappingJackson2JsonView; + +@Configuration +@EnableWebMvc +@PropertySource("classpath:sparklr.properties") +public class WebMvcConfig extends WebMvcConfigurerAdapter { + + @Bean + public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Bean + public ContentNegotiatingViewResolver contentViewResolver() throws Exception { + ContentNegotiatingViewResolver contentViewResolver = new ContentNegotiatingViewResolver(); + ContentNegotiationManagerFactoryBean contentNegotiationManager = new ContentNegotiationManagerFactoryBean(); + contentNegotiationManager.addMediaType("json", MediaType.APPLICATION_JSON); + contentViewResolver.setContentNegotiationManager(contentNegotiationManager.getObject()); + contentViewResolver.setDefaultViews(Arrays. asList(new MappingJackson2JsonView())); + return contentViewResolver; + } + + @Bean + public ViewResolver viewResolver() { + InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); + viewResolver.setPrefix("/WEB-INF/jsp/"); + viewResolver.setSuffix(".jsp"); + return viewResolver; + } + + @Override + public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { + configurer.enable(); + } + + @Bean + public SparklrController sparklrController(@Qualifier("sparklrService") + SparklrService sparklrService) { + SparklrController controller = new SparklrController(); + controller.setSparklrService(sparklrService); + return controller; + } + + @Bean + public SparklrRedirectController sparklrRedirectController(@Qualifier("sparklrRedirectService") + SparklrService sparklrService) { + SparklrRedirectController controller = new SparklrRedirectController(); + controller.setSparklrService(sparklrService); + return controller; + } + + @Bean + public FacebookController facebookController(@Qualifier("facebookRestTemplate") + RestOperations facebookRestTemplate) { + FacebookController controller = new FacebookController(); + controller.setFacebookRestTemplate(facebookRestTemplate); + return controller; + } + + @Bean + public SparklrServiceImpl sparklrService(@Value("${sparklrPhotoListURL}") + String sparklrPhotoListURL, @Value("${sparklrPhotoURLPattern}") + String sparklrPhotoURLPattern, @Value("${sparklrTrustedMessageURL}") + String sparklrTrustedMessageURL, @Qualifier("sparklrRestTemplate") + RestOperations sparklrRestTemplate, @Qualifier("trustedClientRestTemplate") + RestOperations trustedClientRestTemplate) { + SparklrServiceImpl sparklrService = new SparklrServiceImpl(); + sparklrService.setSparklrPhotoListURL(sparklrPhotoListURL); + sparklrService.setSparklrPhotoURLPattern(sparklrPhotoURLPattern); + sparklrService.setSparklrTrustedMessageURL(sparklrTrustedMessageURL); + sparklrService.setSparklrRestTemplate(sparklrRestTemplate); + sparklrService.setTrustedClientRestTemplate(trustedClientRestTemplate); + return sparklrService; + } + + @Bean + public SparklrServiceImpl sparklrRedirectService(@Value("${sparklrPhotoListURL}") + String sparklrPhotoListURL, @Value("${sparklrPhotoURLPattern}") + String sparklrPhotoURLPattern, @Value("${sparklrTrustedMessageURL}") + String sparklrTrustedMessageURL, @Qualifier("sparklrRedirectRestTemplate") + RestOperations sparklrRestTemplate, @Qualifier("trustedClientRestTemplate") + RestOperations trustedClientRestTemplate) { + SparklrServiceImpl sparklrService = new SparklrServiceImpl(); + sparklrService.setSparklrPhotoListURL(sparklrPhotoListURL); + sparklrService.setSparklrPhotoURLPattern(sparklrPhotoURLPattern); + sparklrService.setSparklrTrustedMessageURL(sparklrTrustedMessageURL); + sparklrService.setSparklrRestTemplate(sparklrRestTemplate); + sparklrService.setTrustedClientRestTemplate(trustedClientRestTemplate); + return sparklrService; + } + + @Bean + public ConversionServiceFactoryBean conversionService() { + ConversionServiceFactoryBean conversionService = new ConversionServiceFactoryBean(); + conversionService.setConverters(Collections.singleton(new AccessTokenRequestConverter())); + return conversionService; + } + + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); + } + + @Override + public void configureMessageConverters(List> converters) { + converters.add(new BufferedImageHttpMessageConverter()); + } + + @Configuration + @EnableOAuth2Client + protected static class ResourceConfiguration { + + @Value("${accessTokenUri}") + private String accessTokenUri; + + @Value("${userAuthorizationUri}") + private String userAuthorizationUri; + + @Bean + public OAuth2ProtectedResourceDetails sparklr() { + AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); + details.setId("sparklr/tonr"); + details.setClientId("tonr"); + details.setClientSecret("secret"); + details.setAccessTokenUri(accessTokenUri); + details.setUserAuthorizationUri(userAuthorizationUri); + details.setScope(Arrays.asList("read", "write")); + return details; + } + + @Bean + public OAuth2ProtectedResourceDetails sparklrRedirect() { + AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); + details.setId("sparklr/tonr-redirect"); + details.setClientId("tonr-with-redirect"); + details.setClientSecret("secret"); + details.setAccessTokenUri(accessTokenUri); + details.setUserAuthorizationUri(userAuthorizationUri); + details.setScope(Arrays.asList("read", "write")); + details.setUseCurrentUri(false); + return details; + } + + @Bean + public OAuth2ProtectedResourceDetails facebook() { + AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); + details.setId("facebook"); + details.setClientId("233668646673605"); + details.setClientSecret("33b17e044ee6a4fa383f46ec6e28ea1d"); + details.setAccessTokenUri("https://graph.facebook.com/oauth/access_token"); + details.setUserAuthorizationUri("https://www.facebook.com/dialog/oauth"); + details.setTokenName("oauth_token"); + details.setAuthenticationScheme(AuthenticationScheme.query); + details.setClientAuthenticationScheme(AuthenticationScheme.form); + return details; + } + + @Bean + public OAuth2ProtectedResourceDetails trusted() { + ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); + details.setId("sparklr/trusted"); + details.setClientId("my-client-with-registered-redirect"); + details.setAccessTokenUri(accessTokenUri); + details.setScope(Arrays.asList("trust")); + return details; + } + + @Bean + public OAuth2RestTemplate facebookRestTemplate(OAuth2ClientContext clientContext) { + OAuth2RestTemplate template = new OAuth2RestTemplate(facebook(), clientContext); + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, + MediaType.valueOf("text/javascript"))); + template.setMessageConverters(Arrays.> asList(converter)); + return template; + } + + @Bean + public OAuth2RestTemplate sparklrRestTemplate(OAuth2ClientContext clientContext) { + return new OAuth2RestTemplate(sparklr(), clientContext); + } + + @Bean + public OAuth2RestTemplate sparklrRedirectRestTemplate(OAuth2ClientContext clientContext) { + return new OAuth2RestTemplate(sparklrRedirect(), clientContext); + } + + @Bean + public OAuth2RestTemplate trustedClientRestTemplate() { + return new OAuth2RestTemplate(trusted(), new DefaultOAuth2ClientContext()); + } + + } + +} diff --git a/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/SparklrException.java b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/SparklrException.java index def2bd830..c70ba399c 100644 --- a/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/SparklrException.java +++ b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/SparklrException.java @@ -3,6 +3,7 @@ /** * @author Ryan Heaton */ +@SuppressWarnings("serial") public class SparklrException extends Exception { public SparklrException(String message) { diff --git a/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/converter/AccessTokenRequestConverter.java b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/converter/AccessTokenRequestConverter.java index 4dd475bde..56056fbb9 100644 --- a/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/converter/AccessTokenRequestConverter.java +++ b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/converter/AccessTokenRequestConverter.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 diff --git a/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/impl/SparklrServiceImpl.java b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/impl/SparklrServiceImpl.java index c5b88a1c6..1057d6b8b 100644 --- a/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/impl/SparklrServiceImpl.java +++ b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/impl/SparklrServiceImpl.java @@ -13,7 +13,6 @@ import org.springframework.security.oauth.examples.tonr.SparklrException; import org.springframework.security.oauth.examples.tonr.SparklrService; -import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.web.client.RestOperations; import org.xml.sax.Attributes; import org.xml.sax.SAXException; @@ -81,7 +80,7 @@ public void setSparklrTrustedMessageURL(String sparklrTrustedMessageURL) { this.sparklrTrustedMessageURL = sparklrTrustedMessageURL; } - public void setSparklrRestTemplate(OAuth2RestTemplate sparklrRestTemplate) { + public void setSparklrRestTemplate(RestOperations sparklrRestTemplate) { this.sparklrRestTemplate = sparklrRestTemplate; } diff --git a/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/mvc/FacebookController.java b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/mvc/FacebookController.java index 9df956d8d..7854df129 100644 --- a/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/mvc/FacebookController.java +++ b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/mvc/FacebookController.java @@ -2,15 +2,15 @@ import java.util.ArrayList; -import org.codehaus.jackson.JsonNode; -import org.codehaus.jackson.node.ArrayNode; -import org.codehaus.jackson.node.ObjectNode; -import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.client.RestOperations; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + /** * @author Ryan Heaton * @author Dave Syer @@ -27,13 +27,13 @@ public String photos(Model model) throws Exception { ArrayNode data = (ArrayNode) result.get("data"); ArrayList friends = new ArrayList(); for (JsonNode dataNode : data) { - friends.add(dataNode.get("name").getTextValue()); + friends.add(dataNode.get("name").asText()); } model.addAttribute("friends", friends); return "facebook"; } - public void setFacebookRestTemplate(OAuth2RestTemplate facebookRestTemplate) { + public void setFacebookRestTemplate(RestOperations facebookRestTemplate) { this.facebookRestTemplate = facebookRestTemplate; } diff --git a/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/mvc/SparklrController.java b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/mvc/SparklrController.java index eac4a2147..05e0290e1 100644 --- a/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/mvc/SparklrController.java +++ b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/mvc/SparklrController.java @@ -2,6 +2,7 @@ import java.awt.image.BufferedImage; import java.io.InputStream; +import java.util.Collections; import java.util.Iterator; import javax.imageio.ImageIO; @@ -9,6 +10,7 @@ import javax.imageio.ImageReader; import javax.imageio.stream.MemoryCacheImageInputStream; import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServletRequest; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -20,6 +22,7 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.HandlerMapping; /** * @author Ryan Heaton @@ -37,7 +40,7 @@ public String photos(Model model) throws Exception { } @RequestMapping("/sparklr/photos/{id}") - public ResponseEntity photo(@PathVariable String id) throws Exception { + public ResponseEntity photo(@PathVariable String id, HttpServletRequest request) throws Exception { InputStream photo = sparklrService.loadSparklrPhoto(id); if (photo == null) { throw new UnavailableException("The requested photo does not exist"); @@ -56,6 +59,8 @@ public ResponseEntity photo(@PathVariable String id) throws Excep } HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.IMAGE_JPEG); + request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, + Collections.singleton(MediaType.IMAGE_JPEG)); return new ResponseEntity(body, headers, HttpStatus.OK); } diff --git a/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/mvc/SparklrRedirectController.java b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/mvc/SparklrRedirectController.java new file mode 100644 index 000000000..d28249b58 --- /dev/null +++ b/samples/oauth2/tonr/src/main/java/org/springframework/security/oauth/examples/tonr/mvc/SparklrRedirectController.java @@ -0,0 +1,72 @@ +package org.springframework.security.oauth.examples.tonr.mvc; + +import java.awt.image.BufferedImage; +import java.io.InputStream; +import java.util.Iterator; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.stream.MemoryCacheImageInputStream; +import javax.servlet.UnavailableException; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.security.oauth.examples.tonr.SparklrService; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * @author Ryan Heaton + * @author Dave Syer + */ +@Controller +public class SparklrRedirectController { + + private SparklrService sparklrService; + + @RequestMapping("/sparklr/redirect") + public String photos(Model model) throws Exception { + model.addAttribute("photoIds", sparklrService.getSparklrPhotoIds()); + model.addAttribute("path", "redirect"); + return "sparklr"; + } + + @RequestMapping("/sparklr/trigger") + public String trigger(Model model) throws Exception { + return photos(model); + } + + @RequestMapping("/sparklr/redirect/{id}") + public ResponseEntity photo(@PathVariable String id) throws Exception { + InputStream photo = sparklrService.loadSparklrPhoto(id); + if (photo == null) { + throw new UnavailableException("The requested photo does not exist"); + } + BufferedImage body; + MediaType contentType = MediaType.IMAGE_JPEG; + Iterator imageReaders = ImageIO.getImageReadersByMIMEType(contentType.toString()); + if (imageReaders.hasNext()) { + ImageReader imageReader = imageReaders.next(); + ImageReadParam irp = imageReader.getDefaultReadParam(); + imageReader.setInput(new MemoryCacheImageInputStream(photo), true); + body = imageReader.read(0, irp); + } else { + throw new HttpMessageNotReadableException("Could not find javax.imageio.ImageReader for Content-Type [" + + contentType + "]"); + } + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.IMAGE_JPEG); + return new ResponseEntity(body, headers, HttpStatus.OK); + } + + public void setSparklrService(SparklrService sparklrService) { + this.sparklrService = sparklrService; + } + +} diff --git a/samples/oauth2/tonr/src/main/resources/simplelog.properties b/samples/oauth2/tonr/src/main/resources/simplelog.properties index 0894cf565..cba65bf4c 100644 --- a/samples/oauth2/tonr/src/main/resources/simplelog.properties +++ b/samples/oauth2/tonr/src/main/resources/simplelog.properties @@ -1,6 +1,2 @@ -org.apache.commons.logging.simplelog.defaultlog=info -org.apache.commons.logging.simplelog.showdatetime=true -org.apache.commons.logging.simplelog.dateTimeFormat='tonr2' HH:mm:ss.SSS -org.apache.commons.logging.simplelog.log.org.springframework.security=debug -#org.apache.commons.logging.simplelog.log.org.springframework.security.oauth=info -org.apache.commons.logging.simplelog.log.org.springframework.web=debug +org.apache.commons.logging.simplelog.defaultlog=warn +#org.apache.commons.logging.simplelog.log.org.springframework.security=debug diff --git a/samples/oauth2/tonr/src/main/webapp/LICENSE.txt b/samples/oauth2/tonr/src/main/webapp/LICENSE.txt deleted file mode 100644 index 5c83a364b..000000000 --- a/samples/oauth2/tonr/src/main/webapp/LICENSE.txt +++ /dev/null @@ -1,211 +0,0 @@ -THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE -("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE -OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. - -BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS -OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE -OF SUCH TERMS AND CONDITIONS. - -1. Definitions - - a. "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, - in which the Work in its entirety in unmodified form, along with a number of other - contributions, constituting separate and independent works in themselves, are assembled - into a collective whole. A work that constitutes a Collective Work will not be - considered a Derivative Work (as defined below) for the purposes of this License. - - b. "Derivative Work" means a work based upon the Work or upon the Work and other - pre-existing works, such as a translation, musical arrangement, dramatization, - fictionalization, motion picture version, sound recording, art reproduction, abridgment, - condensation, or any other form in which the Work may be recast, transformed, or adapted, - except that a work that constitutes a Collective Work will not be considered a Derivative - Work for the purpose of this License. For the avoidance of doubt, where the Work is a - musical composition or sound recording, the synchronization of the Work in timed-relation - with a moving image ("synching") will be considered a Derivative Work for the purpose of - this License. - - c. "Licensor" means the individual or entity that offers the Work under the terms of this - License. - - d. "Original Author" means the individual or entity who created the Work. - - e. "Work" means the copyrightable work of authorship offered under the terms of this - License. - - f. "You" means an individual or entity exercising rights under this License who has not - previously violated the terms of this License with respect to the Work, or who has - received express permission from the Licensor to exercise rights under this License - despite a previous violation. - - g. "License Elements" means the following high-level license attributes as selected by - Licensor and indicated in the title of this License: Attribution, ShareAlike. - -2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any rights -arising from fair use, first sale or other limitations on the exclusive rights of the copyright -owner under copyright law or other applicable laws. - -3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants -You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable -copyright) license to exercise the rights in the Work as stated below: - - a. to reproduce the Work, to incorporate the Work into one or more Collective Works, and to - reproduce the Work as incorporated in the Collective Works; - - b. to create and reproduce Derivative Works; - - c. to distribute copies or phonorecords of, display publicly, perform publicly, and perform - publicly by means of a digital audio transmission the Work including as incorporated in - Collective Works; - - d. to distribute copies or phonorecords of, display publicly, perform publicly, and perform - publicly by means of a digital audio transmission Derivative Works. - - e. For the avoidance of doubt, where the work is a musical composition: - - i. Performance Royalties Under Blanket Licenses. Licensor waives the exclusive right to - collect, whether individually or via a performance rights society (e.g. ASCAP, BMI, - SESAC), royalties for the public performance or public digital performance (e.g. - webcast) of the Work. - - ii. Mechanical Rights and Statutory Royalties. Licensor waives the exclusive right to - collect, whether individually or via a music rights society or designated agent (e.g. - Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover - version") and distribute, subject to the compulsory license created by 17 USC Section - 115 of the US Copyright Act (or the equivalent in other jurisdictions). - - f. Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is - a sound recording, Licensor waives the exclusive right to collect, whether individually - or via a performance-rights society (e.g. SoundExchange), royalties for the public - digital performance (e.g. webcast) of the Work, subject to the compulsory license created - by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions). - -The above rights may be exercised in all media and formats whether now known or hereafter devised. -The above rights include the right to make such modifications as are technically necessary to -exercise the rights in other media and formats. All rights not expressly granted by Licensor are -hereby reserved. - -4. Restrictions.The license granted in Section 3 above is expressly made subject to and limited by -the following restrictions: - - a. You may distribute, publicly display, publicly perform, or publicly digitally perform the - Work only under the terms of this License, and You must include a copy of, or the Uniform - Resource Identifier for, this License with every copy or phonorecord of the Work You - distribute, publicly display, publicly perform, or publicly digitally perform. You may - not offer or impose any terms on the Work that alter or restrict the terms of this - License or the recipients' exercise of the rights granted hereunder. You may not - sublicense the Work. You must keep intact all notices that refer to this License and to - the disclaimer of warranties. You may not distribute, publicly display, publicly perform, - or publicly digitally perform the Work with any technological measures that control - access or use of the Work in a manner inconsistent with the terms of this License - Agreement. The above applies to the Work as incorporated in a Collective Work, but this - does not require the Collective Work apart from the Work itself to be made subject to the - terms of this License. If You create a Collective Work, upon notice from any Licensor You - must, to the extent practicable, remove from the Collective Work any reference to such - Licensor or the Original Author, as requested. If You create a Derivative Work, upon - notice from any Licensor You must, to the extent practicable, remove from the Derivative - Work any reference to such Licensor or the Original Author, as requested. - - b. You may distribute, publicly display, publicly perform, or publicly digitally perform a - Derivative Work only under the terms of this License, a later version of this License - with the same License Elements as this License, or a Creative Commons iCommons license - that contains the same License Elements as this License (e.g. Attribution-ShareAlike 2.0 - Japan). You must include a copy of, or the Uniform Resource Identifier for, this License - or other license specified in the previous sentence with every copy or phonorecord of - each Derivative Work You distribute, publicly display, publicly perform, or publicly - digitally perform. You may not offer or impose any terms on the Derivative Works that - alter or restrict the terms of this License or the recipients' exercise of the rights - granted hereunder, and You must keep intact all notices that refer to this License and to - - the disclaimer of warranties. You may not distribute, publicly display, publicly perform, - or publicly digitally perform the Derivative Work with any technological measures that - control access or use of the Work in a manner inconsistent with the terms of this License - Agreement. The above applies to the Derivative Work as incorporated in a Collective Work, - but this does not require the Collective Work apart from the Derivative Work itself to be - made subject to the terms of this License. - - c. If you distribute, publicly display, publicly perform, or publicly digitally perform the - Work or any Derivative Works or Collective Works, You must keep intact all copyright - notices for the Work and give the Original Author credit reasonable to the medium or - means You are utilizing by conveying the name (or pseudonym if applicable) of the - Original Author if supplied; the title of the Work if supplied; to the extent reasonably - practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be - associated with the Work, unless such URI does not refer to the copyright notice or - licensing information for the Work; and in the case of a Derivative Work, a credit - identifying the use of the Work in the Derivative Work (e.g., "French translation of the - Work by Original Author," or "Screenplay based on original Work by Original Author"). - Such credit may be implemented in any reasonable manner; provided, however, that in the - case of a Derivative Work or Collective Work, at a minimum such credit will appear where - any other comparable authorship credit appears and in a manner at least as prominent as - such other comparable authorship credit. - -5. Representations, Warranties and Disclaimer - -UNLESS OTHERWISE AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO -REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE MATERIALS, EXPRESS, IMPLIED, STATUTORY OR -OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A -PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE -PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE -EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. - -6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL -LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE -OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN -ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. Termination - - a. This License and the rights granted hereunder will terminate automatically upon any - breach by You of the terms of this License. Individuals or entities who have received - Derivative Works or Collective Works from You under this License, however, will not have - their licenses terminated provided such individuals or entities remain in full compliance - with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this - License. - - b. Subject to the above terms and conditions, the license granted here is perpetual (for the - duration of the applicable copyright in the Work). Notwithstanding the above, Licensor - reserves the right to release the Work under different license terms or to stop - distributing the Work at any time; provided, however that any such election will not - serve to withdraw this License (or any other license that has been, or is required to be, - granted under the terms of this License), and this License will continue in full force - and effect unless terminated as stated above. - -8. Miscellaneous - - a. Each time You distribute or publicly digitally perform the Work or a Collective Work, the - Licensor offers to the recipient a license to the Work on the same terms and conditions - as the license granted to You under this License. - - b. Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers - to the recipient a license to the original Work on the same terms and conditions as the - license granted to You under this License. - - c. If any provision of this License is invalid or unenforceable under applicable law, it - shall not affect the validity or enforceability of the remainder of the terms of this - License, and without further action by the parties to this agreement, such provision - shall be reformed to the minimum extent necessary to make such provision valid and - enforceable. - - d. No term or provision of this License shall be deemed waived and no breach consented to - unless such waiver or consent shall be in writing and signed by the party to be charged - with such waiver or consent. - - e. This License constitutes the entire agreement between the parties with respect to the - Work licensed here. There are no understandings, agreements or representations with - respect to the Work not specified here. Licensor shall not be bound by any additional - provisions that may appear in any communication from You. This License may not be - modified without the mutual written agreement of the Licensor and You. - -Creative Commons is not a party to this License, and makes no warranty whatsoever in connection -with the Work. Creative Commons will not be liable to You or any party on any legal theory for -any damages whatsoever, including without limitation any general, special, incidental or -consequential damages arising in connection to this license. Notwithstanding the foregoing two -(2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, -it shall have all rights and obligations of Licensor. - -Except for the limited purpose of indicating to the public that the Work is licensed under the -CCPL, neither party will use the trademark "Creative Commons" or any related trademark or logo -of Creative Commons without the prior written consent of Creative Commons. Any permitted use will -be in compliance with Creative Commons' then-current trademark usage guidelines, as may be -published on its website or otherwise made available upon request from time to time. - -Creative Commons may be contacted at http://creativecommons.org/. \ No newline at end of file diff --git a/samples/oauth2/tonr/src/main/webapp/WEB-INF/jsp/facebook.jsp b/samples/oauth2/tonr/src/main/webapp/WEB-INF/jsp/facebook.jsp index b7b1cb9e7..a520e4677 100644 --- a/samples/oauth2/tonr/src/main/webapp/WEB-INF/jsp/facebook.jsp +++ b/samples/oauth2/tonr/src/main/webapp/WEB-INF/jsp/facebook.jsp @@ -1,32 +1,51 @@ -<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags" %> +<%@ taglib prefix="authz" + uri="http://www.springframework.org/security/tags"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> - + - " rel="stylesheet" type="text/css"/> - tonr + + + + + + +tonr -
- - - -
-

Your Facebook Friends:

- -
    - -
  • -
    -
-
-
+ +
+

Your Facebook Friends:

+
    + +
  • +
    +
+
\ No newline at end of file diff --git a/samples/oauth2/tonr/src/main/webapp/WEB-INF/jsp/sparklr.jsp b/samples/oauth2/tonr/src/main/webapp/WEB-INF/jsp/sparklr.jsp index 6cf77d0f2..4d26a524f 100644 --- a/samples/oauth2/tonr/src/main/webapp/WEB-INF/jsp/sparklr.jsp +++ b/samples/oauth2/tonr/src/main/webapp/WEB-INF/jsp/sparklr.jsp @@ -1,32 +1,53 @@ -<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags" %> +<%@ taglib prefix="authz" + uri="http://www.springframework.org/security/tags"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> - + - " rel="stylesheet" type="text/css"/> - tonr + + + + + + +tonr -
+ - - -
-

Your Sparklr Photos

- -
    - -
  • "/>
  • -
    -
-
-
+
+

Your Sparklr Photos

+
    + +
  • +
    +
+
\ No newline at end of file diff --git a/samples/oauth2/tonr/src/main/webapp/WEB-INF/spring-servlet.xml b/samples/oauth2/tonr/src/main/webapp/WEB-INF/spring-servlet.xml deleted file mode 100644 index d58842d12..000000000 --- a/samples/oauth2/tonr/src/main/webapp/WEB-INF/spring-servlet.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/samples/oauth2/tonr/src/main/webapp/WEB-INF/web.xml b/samples/oauth2/tonr/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 712743393..000000000 --- a/samples/oauth2/tonr/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - springSecurityFilterChain - org.springframework.web.filter.DelegatingFilterProxy - - contextAttribute - org.springframework.web.servlet.FrameworkServlet.CONTEXT.spring - - - - - springSecurityFilterChain - /* - - - - spring - org.springframework.web.servlet.DispatcherServlet - 1 - - - - spring - / - - - diff --git a/samples/oauth2/tonr/src/main/webapp/demo.html b/samples/oauth2/tonr/src/main/webapp/demo.html index e12f39150..9ede9ed42 100644 --- a/samples/oauth2/tonr/src/main/webapp/demo.html +++ b/samples/oauth2/tonr/src/main/webapp/demo.html @@ -1,56 +1,62 @@ - + - - - - Tonr JS Client Demo - - - - - - +Tonr JS Client Demo + + + + + + + -

Sparklr Client Authentication Sample

+
-
+

Sparklr Client Authentication Sample

Once you have authenticated and approved the access, some - JavaScript in this page will render a list of photos from Sparklr below:

+ JavaScript in this page will render a list of photos from Sparklr + below:

- diff --git a/samples/oauth2/tonr/src/main/webapp/images/bg.gif b/samples/oauth2/tonr/src/main/webapp/images/bg.gif deleted file mode 100644 index 5cbfaa356..000000000 Binary files a/samples/oauth2/tonr/src/main/webapp/images/bg.gif and /dev/null differ diff --git a/samples/oauth2/tonr/src/main/webapp/images/header.jpg b/samples/oauth2/tonr/src/main/webapp/images/header.jpg deleted file mode 100644 index 4f9014ba9..000000000 Binary files a/samples/oauth2/tonr/src/main/webapp/images/header.jpg and /dev/null differ diff --git a/samples/oauth2/tonr/src/main/webapp/images/xbg.gif b/samples/oauth2/tonr/src/main/webapp/images/xbg.gif deleted file mode 100644 index 9f9cdcabd..000000000 Binary files a/samples/oauth2/tonr/src/main/webapp/images/xbg.gif and /dev/null differ diff --git a/samples/oauth2/tonr/src/main/webapp/index.jsp b/samples/oauth2/tonr/src/main/webapp/index.jsp index 2e4829d81..0c8b14831 100644 --- a/samples/oauth2/tonr/src/main/webapp/index.jsp +++ b/samples/oauth2/tonr/src/main/webapp/index.jsp @@ -1,41 +1,81 @@ -<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags" %> +<%@ taglib prefix="authz" + uri="http://www.springframework.org/security/tags"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> - - + + - " rel="stylesheet" type="text/css"/> - tonr + + + + + + +tonr -
+ - +
-
-

Welcome to Tonr.com!

- -

This is a website that will allow you to print your photos that you've uploaded to sparklr.com! - And since this site uses OAuth to access your photos, we will never ask you - for your Sparklr credentials.

+

Welcome to Tonr!

-

Tonr.com has only two users: "marissa" and "sam". The password for "marissa" is password is "wombat" and for "sam" is password is "kangaroo".

+

+ This is a website that will allow you to print your photos that + you've uploaded to Sparklr! + And since this site uses OAuth to + access your photos, we will never ask you for your Sparklr + credentials. +

- -

">Login to Tonr

-
- -

">View my Sparklr photos

-
+

Tonr.com has only two users: "marissa" and "sam". The password + for "marissa" is password is "wombat" and for "sam" is password is + "kangaroo".

- -
-
+ +

+ ">Login to Tonr +

+
+ +

+ ">View my Sparklr + photos +

+

+

+
+ + +
+
+

+
+ +
\ No newline at end of file diff --git a/samples/oauth2/tonr/src/main/webapp/js/libs/json2.js b/samples/oauth2/tonr/src/main/webapp/js/libs/json2.js deleted file mode 100644 index 2ffe91c7e..000000000 --- a/samples/oauth2/tonr/src/main/webapp/js/libs/json2.js +++ /dev/null @@ -1,487 +0,0 @@ -/* - http://www.JSON.org/json2.js - 2011-10-19 - - Public Domain. - - NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. - - See http://www.JSON.org/js.html - - - This code should be minified before deployment. - See http://javascript.crockford.com/jsmin.html - - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO - NOT CONTROL. - - - This file creates a global JSON object containing two methods: stringify - and parse. - - JSON.stringify(value, replacer, space) - value any JavaScript value, usually an object or array. - - replacer an optional parameter that determines how object - values are stringified for objects. It can be a - function or an array of strings. - - space an optional parameter that specifies the indentation - of nested structures. If it is omitted, the text will - be packed without extra whitespace. If it is a number, - it will specify the number of spaces to indent at each - level. If it is a string (such as '\t' or ' '), - it contains the characters used to indent at each level. - - This method produces a JSON text from a JavaScript value. - - When an object value is found, if the object contains a toJSON - method, its toJSON method will be called and the result will be - stringified. A toJSON method does not serialize: it returns the - value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method - will be passed the key associated with the value, and this will be - bound to the value - - For example, this would serialize Dates as ISO strings. - - Date.prototype.toJSON = function (key) { - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - return this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z'; - }; - - You can provide an optional replacer method. It will be passed the - key and value of each member, with this bound to the containing - object. The value that is returned from your method will be - serialized. If your method returns undefined, then the member will - be excluded from the serialization. - - If the replacer parameter is an array of strings, then it will be - used to select the members to be serialized. It filters the results - such that only members with keys listed in the replacer array are - stringified. - - Values that do not have JSON representations, such as undefined or - functions, will not be serialized. Such values in objects will be - dropped; in arrays they will be replaced with null. You can use - a replacer function to replace those with JSON values. - JSON.stringify(undefined) returns undefined. - - The optional space parameter produces a stringification of the - value that is filled with line breaks and indentation to make it - easier to read. - - If the space parameter is a non-empty string, then that string will - be used for indentation. If the space parameter is a number, then - the indentation will be that many spaces. - - Example: - - text = JSON.stringify(['e', {pluribus: 'unum'}]); - // text is '["e",{"pluribus":"unum"}]' - - - text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); - // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' - - text = JSON.stringify([new Date()], function (key, value) { - return this[key] instanceof Date ? - 'Date(' + this[key] + ')' : value; - }); - // text is '["Date(---current time---)"]' - - - JSON.parse(text, reviver) - This method parses a JSON text to produce an object or array. - It can throw a SyntaxError exception. - - The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, - and its return value is used instead of the original value. - If it returns what it received, then the structure is not modified. - If it returns undefined then the member is deleted. - - Example: - - // Parse the text. Values that look like ISO date strings will - // be converted to Date objects. - - myData = JSON.parse(text, function (key, value) { - var a; - if (typeof value === 'string') { - a = -/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); - if (a) { - return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], - +a[5], +a[6])); - } - } - return value; - }); - - myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { - var d; - if (typeof value === 'string' && - value.slice(0, 5) === 'Date(' && - value.slice(-1) === ')') { - d = new Date(value.slice(5, -1)); - if (d) { - return d; - } - } - return value; - }); - - - This is a reference implementation. You are free to copy, modify, or - redistribute. -*/ - -/*jslint evil: true, regexp: true */ - -/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, - getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, - lastIndex, length, parse, prototype, push, replace, slice, stringify, - test, toJSON, toString, valueOf -*/ - - -// Create a JSON object only if one does not already exist. We create the -// methods in a closure to avoid creating global variables. - -var JSON; -if (!JSON) { - JSON = {}; -} - -(function () { - 'use strict'; - - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - if (typeof Date.prototype.toJSON !== 'function') { - - Date.prototype.toJSON = function (key) { - - return isFinite(this.valueOf()) - ? this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' - : null; - }; - - String.prototype.toJSON = - Number.prototype.toJSON = - Boolean.prototype.toJSON = function (key) { - return this.valueOf(); - }; - } - - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; - - - function quote(string) { - -// If the string contains no control characters, no quote characters, and no -// backslash characters, then we can safely slap some quotes around it. -// Otherwise we must also replace the offending characters with safe escape -// sequences. - - escapable.lastIndex = 0; - return escapable.test(string) ? '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' - ? c - : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : '"' + string + '"'; - } - - - function str(key, holder) { - -// Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - -// If the value has a toJSON method, call it to obtain a replacement value. - - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); - } - -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. - - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } - -// What happens next depends on the value's type. - - switch (typeof value) { - case 'string': - return quote(value); - - case 'number': - -// JSON numbers must be finite. Encode non-finite numbers as null. - - return isFinite(value) ? String(value) : 'null'; - - case 'boolean': - case 'null': - -// If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in -// the remote chance that this gets fixed someday. - - return String(value); - -// If the type is 'object', we might be dealing with an object or an array or -// null. - - case 'object': - -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. - - if (!value) { - return 'null'; - } - -// Make an array to hold the partial results of stringifying this object value. - - gap += indent; - partial = []; - -// Is the value an array? - - if (Object.prototype.toString.apply(value) === '[object Array]') { - -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. - - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } - -// Join all of the elements together, separated with commas, and wrap them in -// brackets. - - v = partial.length === 0 - ? '[]' - : gap - ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' - : '[' + partial.join(',') + ']'; - gap = mind; - return v; - } - -// If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - if (typeof rep[i] === 'string') { - k = rep[i]; - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - -// Otherwise, iterate through all of the keys in the object. - - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } - -// Join all of the member texts together, separated with commas, -// and wrap them in braces. - - v = partial.length === 0 - ? '{}' - : gap - ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' - : '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } - -// If the JSON object does not yet have a stringify method, give it one. - - if (typeof JSON.stringify !== 'function') { - JSON.stringify = function (value, replacer, space) { - -// The stringify method takes a value and an optional replacer, and an optional -// space parameter, and returns a JSON text. The replacer can be a function -// that can replace values, or an array of strings that will select the keys. -// A default replacer method can be provided. Use of the space parameter can -// produce text that is more easily readable. - - var i; - gap = ''; - indent = ''; - -// If the space parameter is a number, make an indent string containing that -// many spaces. - - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } - -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. - - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } - -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. - - return str('', {'': value}); - }; - } - - -// If the JSON object does not yet have a parse method, give it one. - - if (typeof JSON.parse !== 'function') { - JSON.parse = function (text, reviver) { - -// The parse method takes a text and an optional reviver function, and returns -// a JavaScript value if the text is a valid JSON text. - - var j; - - function walk(holder, key) { - -// The walk method is used to recursively walk the resulting structure so -// that modifications can be made. - - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - } - - -// Parsing happens in four stages. In the first stage, we replace certain -// Unicode characters with escape sequences. JavaScript handles many characters -// incorrectly, either silently deleting them, or treating them as line endings. - - text = String(text); - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - -// In the second stage, we run the text against regular expressions that look -// for non-JSON patterns. We are especially concerned with '()' and 'new' -// because they can cause invocation, and '=' because it can cause mutation. -// But just to be safe, we want to reject all unexpected forms. - -// We split the second stage into 4 regexp operations in order to work around -// crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we -// replace all simple value tokens with ']' characters. Third, we delete all -// open brackets that follow a colon or comma or that begin the text. Finally, -// we look to see that the remaining characters are only whitespace or ']' or -// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/ - .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - -// In the third stage we use the eval function to compile the text into a -// JavaScript structure. The '{' operator is subject to a syntactic ambiguity -// in JavaScript: it can begin a block or an object literal. We wrap the text -// in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - -// In the optional fourth stage, we recursively walk the new structure, passing -// each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' - ? walk({'': j}, '') - : j; - } - -// If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); - }; - } -}()); \ No newline at end of file diff --git a/samples/oauth2/tonr/src/main/webapp/js/libs/localstorage.js b/samples/oauth2/tonr/src/main/webapp/js/libs/localstorage.js deleted file mode 100644 index 2e156ab4b..000000000 --- a/samples/oauth2/tonr/src/main/webapp/js/libs/localstorage.js +++ /dev/null @@ -1,3 +0,0 @@ -(function(){if(!this.localStorage)if(this.globalStorage)try{this.localStorage=this.globalStorage}catch(e){}else{var a=document.createElement("div");a.style.display="none";document.getElementsByTagName("head")[0].appendChild(a);if(a.addBehavior){a.addBehavior("#default#userdata");var d=this.localStorage={length:0,setItem:function(b,d){a.load("localStorage");b=c(b);a.getAttribute(b)||this.length++;a.setAttribute(b,d);a.save("localStorage")},getItem:function(b){a.load("localStorage");b=c(b);return a.getAttribute(b)}, -removeItem:function(b){a.load("localStorage");b=c(b);a.removeAttribute(b);a.save("localStorage");this.length--;if(0>this.length)this.length=0},clear:function(){a.load("localStorage");for(var b=0;attr=a.XMLDocument.documentElement.attributes[b++];)a.removeAttribute(attr.name);a.save("localStorage");this.length=0},key:function(b){a.load("localStorage");return a.XMLDocument.documentElement.attributes[b]}},c=function(a){return a.replace(/[^-._0-9A-Za-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u37f-\u1fff\u200c-\u200d\u203f\u2040\u2070-\u218f]/g, -"-")};a.load("localStorage");d.length=a.XMLDocument.documentElement.attributes.length}}})(); \ No newline at end of file diff --git a/samples/oauth2/tonr/src/main/webapp/js/libs/modernizr-2.5.3.min.js b/samples/oauth2/tonr/src/main/webapp/js/libs/modernizr-2.5.3.min.js deleted file mode 100755 index 27fcdae82..000000000 --- a/samples/oauth2/tonr/src/main/webapp/js/libs/modernizr-2.5.3.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/* Modernizr 2.5.3 (Custom Build) | MIT & BSD - * Build: http://www.modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load - */ -;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a)if(j[a[d]]!==c)return b=="pfx"?a[d]:!0;return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.substr(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function L(){e.input=function(c){for(var d=0,e=c.length;d",a,""].join(""),k.id=h,m.innerHTML+=f,m.appendChild(k),l||(m.style.background="",g.appendChild(m)),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e});var K=function(c,d){var f=c.join(""),g=d.length;y(f,function(c,d){var f=b.styleSheets[b.styleSheets.length-1],h=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"",i=c.childNodes,j={};while(g--)j[i[g].id]=i[g];e.touch="ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch||(j.touch&&j.touch.offsetTop)===9,e.csstransforms3d=(j.csstransforms3d&&j.csstransforms3d.offsetLeft)===9&&j.csstransforms3d.offsetHeight===3,e.generatedcontent=(j.generatedcontent&&j.generatedcontent.offsetHeight)>=1,e.fontface=/src/i.test(h)&&h.indexOf(d.split(" ")[0])===0},g,d)}(['@font-face {font-family:"font";src:url("https://")}',["@media (",n.join("touch-enabled),("),h,")","{#touch{top:9px;position:absolute}}"].join(""),["@media (",n.join("transform-3d),("),h,")","{#csstransforms3d{left:9px;position:absolute;height:3px;}}"].join(""),['#generatedcontent:after{content:"',l,'";visibility:hidden}'].join("")],["fontface","touch","csstransforms3d","generatedcontent"]);s.flexbox=function(){return J("flexOrder")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){try{var d=b.createElement("canvas"),e;e=!(!a.WebGLRenderingContext||!d.getContext("experimental-webgl")&&!d.getContext("webgl")),d=c}catch(f){e=!1}return e},s.touch=function(){return e.touch},s.geolocation=function(){return!!navigator.geolocation},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){for(var b=-1,c=p.length;++b",d.insertBefore(c.lastChild,d.firstChild)}function h(){var a=k.elements;return typeof a=="string"?a.split(" "):a}function i(a){var b={},c=a.createElement,e=a.createDocumentFragment,f=e();a.createElement=function(a){var e=(b[a]||(b[a]=c(a))).cloneNode();return k.shivMethods&&e.canHaveChildren&&!d.test(a)?f.appendChild(e):e},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+h().join().replace(/\w+/g,function(a){return b[a]=c(a),f.createElement(a),'c("'+a+'")'})+");return n}")(k,f)}function j(a){var b;return a.documentShived?a:(k.shivCSS&&!e&&(b=!!g(a,"article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio{display:none}canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden]{display:none}audio[controls]{display:inline-block;*display:inline;*zoom:1}mark{background:#FF0;color:#000}")),f||(b=!i(a)),b&&(a.documentShived=b),a)}var c=a.html5||{},d=/^<|^(?:button|form|map|select|textarea)$/i,e,f;(function(){var a=b.createElement("a");a.innerHTML="",e="hidden"in a,f=a.childNodes.length==1||function(){try{b.createElement("a")}catch(a){return!0}var c=b.createDocumentFragment();return typeof c.cloneNode=="undefined"||typeof c.createDocumentFragment=="undefined"||typeof c.createElement=="undefined"}()})();var k={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:j};a.html5=k,j(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return o.call(a)=="[object Function]"}function e(a){return typeof a=="string"}function f(){}function g(a){return!a||a=="loaded"||a=="complete"||a=="uninitialized"}function h(){var a=p.shift();q=1,a?a.t?m(function(){(a.t=="c"?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){a!="img"&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l={},o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};y[c]===1&&(r=1,y[c]=[],l=b.createElement(a)),a=="object"?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),a!="img"&&(r||y[c]===2?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i(b=="c"?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),p.length==1&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&o.call(a.opera)=="[object Opera]",l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return o.call(a)=="[object Array]"},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f - - + + -" rel="stylesheet" type="text/css" /> + + + + + + tonr -
- - - -
- -

Woops!

- -

Your login attempt was not successful.

-
- -

Woops!

- -

You are not permitted to access that resource.

-
- -

Login

- -

Tonr.com has only two users: "marissa" and "sam". The password - for "marissa" is password is "wombat" and for "sam" is password is - "kangaroo".

- -
" method="post"> -

- -

-

- -

- -

- -

-
- - + + + +
+ + +

Woops!

+ +

Your login attempt was not successful.

+
+ +

Woops!

+ +

You are not permitted to access that resource.

+
+ +

Tonr.com has only two users: "marissa" and "sam". The password + for "marissa" is password is "wombat" and for "sam" is password is + "kangaroo".

+ +
+
+ +

Login

+
+
+ +
+
+ +
+ + +
+
+ +
\ No newline at end of file diff --git a/samples/oauth2/tonr/src/main/webapp/main.css b/samples/oauth2/tonr/src/main/webapp/main.css deleted file mode 100644 index 8421c3edb..000000000 --- a/samples/oauth2/tonr/src/main/webapp/main.css +++ /dev/null @@ -1,102 +0,0 @@ -body { - font-size: 10pt; - letter-spacing: 1px; - background: url(http://github.com/images/xbg.gif) no-repeat; - font-family: Helvetica, Arial, sans-serif; - word-spacing: 5px; - color: #515151; -} - -#container { - margin: 100px auto 0 auto; - width: 855px; -} - -#content { - background-color: #f7f7f7; - padding: 20px 0 20px 20px; -} - -#mainlinks { - height: 60px; - list-style: none; - margin: 0; -} - -#mainlinks li { - float: right; - margin-right: 10px; - list-style-type: none; - list-style-image: none; -} - -#mainlinks li a { - padding: 30px 0 0 0; - background: #d0d0d0; - display: block; - padding-top: 10px; - width: 100px; - height: 50px; - text-align: center; - color: #515151; - text-decoration: none; -} - -#mainlinks li a:hover { - background: #f7f7f7; -} - -#mainlinks li a.selected { - background: #f7f7f7; - color: #515151; -} - -p { - line-height: 1.4em; -} - -p.error { - color: red; -} - -h1 { - padding: 25px 0 5px 0; -} - -#picturelist { - text-align: left; - margin-right: auto; - margin-left: auto; -} - -#picturelist li { - list-style: none; - margin: 42px; - font-weight: bold; - color: #777777; -} - -#picturelist img { - display: block; - border: 2px solid gray; -} - -#picturelist img:hover { - border: 2px solid #515151; -} - -#picturelist a { - color: #777777; - text-decoration: none; -} - -#picturelist a:hover { - color: #999999; -} - -.footer { - margin-top: 100px; - font-size: .8em; - text-align:center; - -} \ No newline at end of file diff --git a/samples/oauth2/tonr/src/main/webapp/oauth_error.jsp b/samples/oauth2/tonr/src/main/webapp/oauth_error.jsp index dcf5d5ca0..ac9accec8 100644 --- a/samples/oauth2/tonr/src/main/webapp/oauth_error.jsp +++ b/samples/oauth2/tonr/src/main/webapp/oauth_error.jsp @@ -1,29 +1,39 @@ -<%@ page import="java.io.PrintWriter" %> -<%@ page import="java.io.StringWriter" %> -<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> - - +<%@ taglib prefix="authz" + uri="http://www.springframework.org/security/tags"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core"%> + + - " rel="stylesheet" type="text/css"/> - tonr + + + + + + -
- - - -
- -
-
+ +
\ No newline at end of file diff --git a/samples/oauth2/tonr/src/main/webapp/style.css b/samples/oauth2/tonr/src/main/webapp/style.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/samples/oauth2/tonr/src/main/webapp/template.html b/samples/oauth2/tonr/src/main/webapp/template.html deleted file mode 100644 index 895b8efaf..000000000 --- a/samples/oauth2/tonr/src/main/webapp/template.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - xgallery - - -
- - - -
-
    -
  • a treea tree
  • -
  • the seathe sea
  • -
  • the forestthe forest
  • -
  • a bunch of orange flowersa bunch of orange flowers
  • -
  • a turtle in the seaa turtle in the sea
  • -
  • a garden/jungle thinga garden/jungle thing
  • -
  • an oryx antelope in the desertan oryx antelope in the desert
  • -
  • mesas in the desertmesas in the desert Courtesy Open - Web DesignThanks -to Dubai Hotels
  • -
-
-
- - \ No newline at end of file diff --git a/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/AuthorizationCodeGrantTests.java b/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/AuthorizationCodeGrantTests.java new file mode 100755 index 000000000..4d8c9c5cc --- /dev/null +++ b/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/AuthorizationCodeGrantTests.java @@ -0,0 +1,184 @@ +package org.springframework.security.oauth.examples.tonr; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; +import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * @author Ryan Heaton + * @author Dave Syer + */ +public class AuthorizationCodeGrantTests { + + @Rule + public ServerRunning serverRunning = ServerRunning.isRunning(); + + private AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); + + { + resource.setAccessTokenUri(serverRunning + .getUrl("/sparklr2/oauth/token")); + resource.setClientId("my-client-with-registered-redirect"); + resource.setId("sparklr"); + resource.setScope(Arrays.asList("trust")); + resource.setUserAuthorizationUri(serverRunning + .getUrl("/sparklr2/oauth/authorize")); + } + + @Test + public void testCannotConnectWithoutToken() throws Exception { + + OAuth2RestTemplate template = new OAuth2RestTemplate(resource); + resource.setPreEstablishedRedirectUri("https://anywhere.com/"); + try { + template.getForObject(serverRunning.getUrl("/tonr2/photos"), + String.class); + fail("Expected UserRedirectRequiredException"); + } catch (UserRedirectRequiredException e) { + String message = e.getMessage(); + assertTrue( + "Wrong message: " + message, + message.contains("A redirect is required to get the users approval")); + } + } + + @Test + public void testAttemptedTokenAcquisitionWithNoRedirect() throws Exception { + AuthorizationCodeAccessTokenProvider provider = new AuthorizationCodeAccessTokenProvider(); + try { + OAuth2AccessToken token = provider.obtainAccessToken(resource, + new DefaultAccessTokenRequest()); + fail("Expected UserRedirectRequiredException"); + assertNotNull(token); + } catch (UserRedirectRequiredException e) { + String message = e.getMessage(); + assertTrue("Wrong message: " + message, + message.contains("A redirect is required")); + } + } + + @Test + public void testTokenAcquisitionWithCorrectContext() throws Exception { + + ResponseEntity page = serverRunning.getForString("/tonr2/login.jsp"); + String cookie = page.getHeaders().getFirst("Set-Cookie"); + HttpHeaders headers = new HttpHeaders(); + headers.set("Cookie", cookie); + Matcher matcher = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*").matcher(page.getBody()); + + MultiValueMap form; + form = new LinkedMultiValueMap(); + form.add("username", "marissa"); + form.add("password", "wombat"); + if (matcher.matches()) { + form.add("_csrf", matcher.group(1)); + } + ResponseEntity response = serverRunning.postForStatus("/tonr2/login", headers, + form); + cookie = response.getHeaders().getFirst("Set-Cookie"); + + headers = new HttpHeaders(); + headers.set("Cookie", cookie); + // headers.setAccept(Collections.singletonList(MediaType.ALL)); + headers.setAccept(MediaType + .parseMediaTypes("image/png,image/*;q=0.8,*/*;q=0.5")); + + String location = serverRunning.getForRedirect( + "/tonr2/sparklr/photos/1", headers); + location = authenticateAndApprove(location); + + assertTrue("Redirect location should be to the original photo URL: " + + location, location.contains("photos/1")); + HttpStatus status = serverRunning.getStatusCode(location, headers); + assertEquals(HttpStatus.OK, status); + } + + @Test + public void testTokenAcquisitionWithRegisteredRedirect() throws Exception { + + ResponseEntity page = serverRunning.getForString("/tonr2/login.jsp"); + String cookie = page.getHeaders().getFirst("Set-Cookie"); + HttpHeaders headers = new HttpHeaders(); + headers.set("Cookie", cookie); + Matcher matcher = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*").matcher(page.getBody()); + + MultiValueMap form; + form = new LinkedMultiValueMap(); + form.add("username", "marissa"); + form.add("password", "wombat"); + if (matcher.matches()) { + form.add("_csrf", matcher.group(1)); + } + ResponseEntity response = serverRunning.postForStatus("/tonr2/login", headers, + form); + cookie = response.getHeaders().getFirst("Set-Cookie"); + + headers = new HttpHeaders(); + headers.set("Cookie", cookie); + + // The registered redirect is /redirect, but /trigger is used as a test + // because it is different (i.e. not the current request URI). + String location = serverRunning.getForRedirect( + "/tonr2/sparklr/trigger", headers); + location = authenticateAndApprove(location); + + assertTrue("Redirect location should be to the original photo URL: " + + location, location.contains("sparklr/redirect")); + HttpStatus status = serverRunning.getStatusCode(location, headers); + assertEquals(HttpStatus.OK, status); + } + + private String authenticateAndApprove(String location) { + + ResponseEntity page = serverRunning.getForString("/sparklr2/login.jsp"); + String cookie = page.getHeaders().getFirst("Set-Cookie"); + Matcher matcher = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*").matcher(page.getBody()); + + MultiValueMap form; + form = new LinkedMultiValueMap(); + form.add("username", "marissa"); + form.add("password", "koala"); + if (matcher.matches()) { + form.add("_csrf", matcher.group(1)); + } + + HttpHeaders response = serverRunning.postForHeaders( + "/sparklr2/login", form); + + cookie = response.getFirst("Set-Cookie"); + HttpHeaders headers = new HttpHeaders(); + headers.set("Cookie", cookie); + + serverRunning.getForString(location, headers); + // Should be on user approval page now + form = new LinkedMultiValueMap(); + form.add("user_oauth_approval", "true"); + form.add("scope.read", "true"); + response = serverRunning.postForHeaders("/sparklr2/oauth/authorize", + form, headers); + + return response.getLocation().toString(); + } + +} diff --git a/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestClientCredentialsGrant.java b/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/ClientCredentialsGrantTests.java similarity index 94% rename from samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestClientCredentialsGrant.java rename to samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/ClientCredentialsGrantTests.java index 90b16ccaa..2b4a3c916 100644 --- a/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestClientCredentialsGrant.java +++ b/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/ClientCredentialsGrantTests.java @@ -1,5 +1,6 @@ package org.springframework.security.oauth.examples.tonr; + import static org.junit.Assert.assertEquals; import java.util.Arrays; @@ -18,7 +19,7 @@ * @author Ryan Heaton * @author Dave Syer */ -public class TestClientCredentialsGrant { +public class ClientCredentialsGrantTests { @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); @@ -36,10 +37,8 @@ public void testConnectDirectlyToResourceServer() throws Exception { ClientCredentialsAccessTokenProvider provider = new ClientCredentialsAccessTokenProvider(); OAuth2AccessToken accessToken = provider.obtainAccessToken(resource, new DefaultAccessTokenRequest()); - // TODO: should this work? The client id is different. OAuth2RestTemplate template = new OAuth2RestTemplate(resource, new DefaultOAuth2ClientContext(accessToken)); String result = template.getForObject(serverRunning.getUrl("/sparklr2/photos/trusted/message"), String.class); - // System.err.println(result); assertEquals("Hello, Trusted Client", result); } diff --git a/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestRefreshTokenGrant.java b/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/RefreshTokenGrantTests.java similarity index 98% rename from samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestRefreshTokenGrant.java rename to samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/RefreshTokenGrantTests.java index 366c4579e..655e23f0b 100644 --- a/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestRefreshTokenGrant.java +++ b/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/RefreshTokenGrantTests.java @@ -1,5 +1,6 @@ package org.springframework.security.oauth.examples.tonr; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -25,7 +26,7 @@ /** * @author Dave Syer */ -public class TestRefreshTokenGrant { +public class RefreshTokenGrantTests { @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); diff --git a/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestResourceOwnerPasswordGrant.java b/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/ResourceOwnerPasswordGrantTests.java similarity index 96% rename from samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestResourceOwnerPasswordGrant.java rename to samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/ResourceOwnerPasswordGrantTests.java index 1cff142c6..cde710db1 100644 --- a/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestResourceOwnerPasswordGrant.java +++ b/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/ResourceOwnerPasswordGrantTests.java @@ -1,5 +1,6 @@ package org.springframework.security.oauth.examples.tonr; + import static org.junit.Assert.assertEquals; import java.util.Arrays; @@ -12,7 +13,7 @@ /** * @author Dave Syer */ -public class TestResourceOwnerPasswordGrant { +public class ResourceOwnerPasswordGrantTests { @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); diff --git a/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/ServerRunning.java b/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/ServerRunning.java index a33bcd64e..ffc3c28f4 100755 --- a/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/ServerRunning.java +++ b/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/ServerRunning.java @@ -1,5 +1,6 @@ package org.springframework.security.oauth.examples.tonr; + import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; @@ -205,7 +206,7 @@ public ResponseEntity postForStatus(String path, HttpHeaders headers, Mult actualHeaders.putAll(headers); actualHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); return client.exchange(getUrl(path), HttpMethod.POST, new HttpEntity>(formData, - actualHeaders), null); + actualHeaders), (Class)null); } public HttpHeaders postForHeaders(String path, MultiValueMap formData) { diff --git a/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestAuthorizationCodeGrant.java b/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestAuthorizationCodeGrant.java deleted file mode 100755 index b27838f72..000000000 --- a/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestAuthorizationCodeGrant.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.springframework.security.oauth.examples.tonr; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.util.Arrays; - -import org.junit.Rule; -import org.junit.Test; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.security.oauth2.client.OAuth2RestTemplate; -import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; -import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; -import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; -import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -/** - * @author Ryan Heaton - * @author Dave Syer - */ -public class TestAuthorizationCodeGrant { - - @Rule - public ServerRunning serverRunning = ServerRunning.isRunning(); - - private AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); - - { - resource.setAccessTokenUri(serverRunning.getUrl("/sparklr2/oauth/token")); - resource.setClientId("my-client-with-registered-redirect"); - resource.setId("sparklr"); - resource.setScope(Arrays.asList("trust")); - resource.setUserAuthorizationUri(serverRunning.getUrl("/sparklr2/oauth/authorize")); - } - - @Test - public void testCannotConnectWithoutToken() throws Exception { - - OAuth2RestTemplate template = new OAuth2RestTemplate(resource); - resource.setPreEstablishedRedirectUri("http://anywhere.com/"); - try { - template.getForObject(serverRunning.getUrl("/tonr2/photos"), String.class); - fail("Expected UserRedirectRequiredException"); - } - catch (UserRedirectRequiredException e) { - String message = e.getMessage(); - assertTrue("Wrong message: " + message, - message.contains("A redirect is required to get the users approval")); - } - } - - @Test - public void testAttemptedTokenAcquisitionWithNoRedirect() throws Exception { - AuthorizationCodeAccessTokenProvider provider = new AuthorizationCodeAccessTokenProvider(); - try { - OAuth2AccessToken token = provider.obtainAccessToken(resource, new DefaultAccessTokenRequest()); - fail("Expected UserRedirectRequiredException"); - assertNotNull(token); - } - catch (IllegalStateException e) { - String message = e.getMessage(); - assertTrue("Wrong message: " + message, message.contains("No redirect URI has been established")); - } - } - - @Test - public void testTokenAcquisitionWithCorrectContext() throws Exception { - - MultiValueMap form = new LinkedMultiValueMap(); - form.add("j_username", "marissa"); - form.add("j_password", "wombat"); - HttpHeaders response = serverRunning.postForHeaders("/tonr2/login.do", form); - String cookie = response.getFirst("Set-Cookie"); - - HttpHeaders headers = new HttpHeaders(); - headers.set("Cookie", cookie); - // headers.setAccept(Collections.singletonList(MediaType.ALL)); - headers.setAccept(MediaType.parseMediaTypes("image/png,image/*;q=0.8,*/*;q=0.5")); - - String location = serverRunning.getForRedirect("/tonr2/sparklr/photos/1", headers); - location = authenticateAndApprove(location); - - assertTrue("Redirect location should be to the original photo URL: " + location, location.contains("photos/1")); - HttpStatus status = serverRunning.getStatusCode(location, headers); - assertEquals(HttpStatus.OK, status); - } - - private String authenticateAndApprove(String location) { - - // First authenticate and grab the cookie - MultiValueMap form = new LinkedMultiValueMap(); - form.add("j_username", "marissa"); - form.add("j_password", "koala"); - HttpHeaders response = serverRunning.postForHeaders("/sparklr2/login.do", form); - - String cookie = response.getFirst("Set-Cookie"); - HttpHeaders headers = new HttpHeaders(); - headers.set("Cookie", cookie); - - serverRunning.getForString(location, headers); - // Should be on user approval page now - form = new LinkedMultiValueMap(); - form.add("user_oauth_approval", "true"); - response = serverRunning.postForHeaders("/sparklr2/oauth/authorize", form, headers); - - return response.getLocation().toString(); - } - -} diff --git a/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestBootstrap.java b/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestBootstrap.java deleted file mode 100755 index ebd6961db..000000000 --- a/samples/oauth2/tonr/src/test/java/org/springframework/security/oauth/examples/tonr/TestBootstrap.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2006-2010 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package org.springframework.security.oauth.examples.tonr; - -import org.junit.Test; -import org.springframework.context.support.GenericXmlApplicationContext; -import org.springframework.core.io.FileSystemResource; - -/** - * @author Dave Syer - * - */ -public class TestBootstrap { - - @Test - public void testRootContext() throws Exception { - GenericXmlApplicationContext context = new GenericXmlApplicationContext(new FileSystemResource("src/main/webapp/WEB-INF/spring-servlet.xml")); - context.close(); - } - -} diff --git a/samples/oauth2/tonr/src/test/java/org/springframework/security/samples/config/AdHocTests.java b/samples/oauth2/tonr/src/test/java/org/springframework/security/samples/config/AdHocTests.java new file mode 100644 index 000000000..8133a76db --- /dev/null +++ b/samples/oauth2/tonr/src/test/java/org/springframework/security/samples/config/AdHocTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 20013-2014 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.samples.config; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; +import org.springframework.security.oauth.examples.tonr.AuthorizationCodeGrantTests; +import org.springframework.security.oauth.examples.tonr.ClientCredentialsGrantTests; +import org.springframework.security.oauth.examples.tonr.RefreshTokenGrantTests; +import org.springframework.security.oauth.examples.tonr.ResourceOwnerPasswordGrantTests; + +/** + * @author Dave Syer + * + */ +@RunWith(Suite.class) +// @formatter:off +@SuiteClasses({ + SecurityConfigTests.class, + ClientCredentialsGrantTests.class, + RefreshTokenGrantTests.class, + ResourceOwnerPasswordGrantTests.class, + AuthorizationCodeGrantTests.class, + }) +// @formatter:on +// @Ignore("Test suite for tracking order dependencies") +public class AdHocTests { + +} diff --git a/samples/oauth2/tonr/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java b/samples/oauth2/tonr/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java new file mode 100644 index 000000000..b08ee85a3 --- /dev/null +++ b/samples/oauth2/tonr/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.samples.config; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +/** + * @author Rob Winch + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@WebAppConfiguration +@DirtiesContext +public class SecurityConfigTests { + @Configuration + @ComponentScan(basePackages = "org.springframework.security.oauth.examples.config") + public static class Config {} + + @Autowired + private FilterChainProxy springSecurityFilterChain; + + @Test + public void securityConfigurationLoads() {} +} diff --git a/samples/pom.xml b/samples/pom.xml index dc727a154..4f4e8cf28 100755 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -1,10 +1,10 @@ - + 4.0.0 org.springframework.security.oauth spring-security-oauth-parent - 1.0.1.BUILD-SNAPSHOT + 2.5.3.BUILD-SNAPSHOT spring-security-oauth-samples @@ -19,7 +19,7 @@ oauth2/tonr - http://static.springframework.org/spring-security/oauth/samples + https://docs.spring.io/spring-security/oauth/samples @@ -37,8 +37,8 @@ - static.springframework.org - scp://static.springframework.org/var/www/domains/springframework.org/static/htdocs/spring-security/oauth/samples + static.spring.io + scp://docs-ip.spring.io/var/www/domains/spring.io/docs/htdocs/spring-security/oauth/site/docs/${project.version} diff --git a/spring-security-jwt/.mvn/jvm.config b/spring-security-jwt/.mvn/jvm.config new file mode 100644 index 000000000..0e7dabeff --- /dev/null +++ b/spring-security-jwt/.mvn/jvm.config @@ -0,0 +1 @@ +-Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom \ No newline at end of file diff --git a/spring-security-jwt/.mvn/maven.config b/spring-security-jwt/.mvn/maven.config new file mode 100644 index 000000000..3b8cf46e1 --- /dev/null +++ b/spring-security-jwt/.mvn/maven.config @@ -0,0 +1 @@ +-DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local -P spring diff --git a/spring-security-jwt/.mvn/wrapper/maven-wrapper.jar b/spring-security-jwt/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 000000000..5fd4d5023 Binary files /dev/null and b/spring-security-jwt/.mvn/wrapper/maven-wrapper.jar differ diff --git a/spring-security-jwt/.mvn/wrapper/maven-wrapper.properties b/spring-security-jwt/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..eb9194764 --- /dev/null +++ b/spring-security-jwt/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.3/apache-maven-3.3.3-bin.zip \ No newline at end of file diff --git a/spring-security-jwt/mvnw b/spring-security-jwt/mvnw new file mode 100755 index 000000000..02f96acef --- /dev/null +++ b/spring-security-jwt/mvnw @@ -0,0 +1,243 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +VERSION=$(awk '/ 0) {$0=$0} 1' `dirname $0`/pom.xml| grep '\(.*\)<.*/\1/') +if echo $VERSION | egrep -q 'M|RC'; then + echo Activating \"milestone\" profile for version=\"$VERSION\" + echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone" +else + echo Deactivating \"milestone\" profile for version=\"$VERSION\" + echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//') +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} ${MAVEN_ARGS} "$@" + diff --git a/spring-security-jwt/mvnw.cmd b/spring-security-jwt/mvnw.cmd new file mode 100644 index 000000000..eb9a292a7 --- /dev/null +++ b/spring-security-jwt/mvnw.cmd @@ -0,0 +1,145 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/spring-security-jwt/pom.xml b/spring-security-jwt/pom.xml index 35383d500..87b274878 100755 --- a/spring-security-jwt/pom.xml +++ b/spring-security-jwt/pom.xml @@ -1,55 +1,48 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0%20https://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 org.springframework.security spring-security-jwt - 1.0.1.BUILD-SNAPSHOT + 1.1.2.BUILD-SNAPSHOT jar Spring Security JWT Library - Spring Security JWT is a small utility library for encoding and decoding JSON Web Tokens. - It belongs to the family of Spring Security crypto libraries that handle encoding and decoding text as + Spring Security JWT is a small utility library for encoding and decoding JSON Web Tokens. + It belongs to the family of Spring Security crypto libraries that handle encoding and decoding text as a general, useful thing to be able to do. - http://github.com/SpringSource/spring-security-oauth + https://github.com/spring-projects/spring-security-oauth SpringSource - http://www.springsource.com + https://www.springsource.com Apache 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt + https://www.apache.org/licenses/LICENSE-2.0.txt - - org.codehaus.jackson - jackson-mapper-asl - 1.8.3 - - org.bouncycastle bcpkix-jdk15on - 1.47 + 1.69 - junit junit - 4.8.2 + 4.11 test org.mockito mockito-all - 1.9.0 + 1.9.5 test @@ -57,7 +50,7 @@ org.jruby jruby - 1.6.5 + 1.7.8 test @@ -75,7 +68,8 @@ **/*Tests.java - + @@ -83,9 +77,9 @@ - org.springframework.build.aws - org.springframework.build.aws.maven - 3.0.0.RELEASE + org.springframework.build + aws-maven + 5.0.0.RELEASE @@ -94,8 +88,8 @@ maven-compiler-plugin 2.3.2 - 1.5 - 1.5 + 1.6 + 1.6 @@ -130,20 +124,20 @@ - static.springframework.org - scp://static.springframework.org/var/www/domains/springframework.org/static/htdocs/spring-security/oauth + static.spring.io + scp://docs-ip.spring.io/var/www/domains/spring.io/docs/htdocs/spring-security/oauth/site/docs/${project.version} - spring-release + repo.spring.io Spring Release Repository - s3://maven.springframework.org/release + https://repo.spring.io/libs-release-local - spring-snapshot + repo.spring.io Spring Snapshot Repository - s3://maven.springframework.org/snapshot + https://repo.spring.io/libs-snapshot-local @@ -184,9 +178,9 @@ - http://github.com/SpringSource/spring-security-oauth - scm:git:git://github.com/SpringSource/spring-security-oauth.git - scm:git:ssh://git@github.com/SpringSource/spring-security-oauth.git + https://github.com/spring-projects/spring-security-oauth + scm:git:git://github.com/spring-projects/spring-security-oauth.git + scm:git:ssh://git@github.com/spring-projects/spring-security-oauth.git diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/AlgorithmMetadata.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/AlgorithmMetadata.java index d4d72867d..da7775c2c 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/AlgorithmMetadata.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/AlgorithmMetadata.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -13,8 +13,12 @@ package org.springframework.security.jwt; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Luke Taylor */ +@Deprecated public interface AlgorithmMetadata { /** * @return the JCA/JCE algorithm name. diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/BinaryFormat.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/BinaryFormat.java index aaded18a0..c570c6599 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/BinaryFormat.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/BinaryFormat.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -13,8 +13,12 @@ package org.springframework.security.jwt; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Luke Taylor */ +@Deprecated public interface BinaryFormat { byte[] bytes(); } diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/Jwt.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/Jwt.java index be3b253ef..1e1542a03 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/Jwt.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/Jwt.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -15,8 +15,12 @@ import org.springframework.security.jwt.crypto.sign.SignatureVerifier; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Luke Taylor */ +@Deprecated public interface Jwt extends BinaryFormat { String getClaims(); diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/JwtAlgorithms.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/JwtAlgorithms.java index 6d837c6e7..4ea77b416 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/JwtAlgorithms.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/JwtAlgorithms.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -18,8 +18,12 @@ import org.springframework.security.jwt.crypto.cipher.CipherMetadata; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Luke Taylor */ +@Deprecated public class JwtAlgorithms { private static final Map sigAlgs = new HashMap(); private static final Map javaToSigAlgs = new HashMap(); diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/JwtHelper.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/JwtHelper.java index 4ebd7bc26..04e743efb 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/JwtHelper.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/JwtHelper.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -19,33 +19,37 @@ import static org.springframework.security.jwt.codec.Codecs.utf8Decode; import static org.springframework.security.jwt.codec.Codecs.utf8Encode; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.nio.CharBuffer; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonGenerator; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; import org.springframework.security.jwt.crypto.sign.SignatureVerifier; import org.springframework.security.jwt.crypto.sign.Signer; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Luke Taylor + * @author Dave Syer */ +@Deprecated public class JwtHelper { static byte[] PERIOD = utf8Encode("."); /** * Creates a token from an encoded token string. * - * @param token the (non-null) encoded token (three Base-64 encoded strings separated by "." characters) + * @param token the (non-null) encoded token (three Base-64 encoded strings separated + * by "." characters) */ public static Jwt decode(String token) { int firstPeriod = token.indexOf('.'); int lastPeriod = token.lastIndexOf('.'); - if (firstPeriod <=0 || lastPeriod <= firstPeriod) { + if (firstPeriod <= 0 || lastPeriod <= firstPeriod) { throw new IllegalArgumentException("JWT must have 3 tokens"); } CharBuffer buffer = CharBuffer.wrap(token, 0, firstPeriod); @@ -60,10 +64,12 @@ public static Jwt decode(String token) { if (emptyCrypto) { if (!"none".equals(header.parameters.alg)) { - throw new IllegalArgumentException("Signed or encrypted token must have non-empty crypto segment"); + throw new IllegalArgumentException( + "Signed or encrypted token must have non-empty crypto segment"); } crypto = new byte[0]; - } else { + } + else { buffer.limit(token.length()).position(lastPeriod + 1); crypto = b64UrlDecode(buffer); } @@ -76,11 +82,27 @@ public static Jwt decodeAndVerify(String token, SignatureVerifier verifier) { return jwt; } + + public static Map headers(String token) { + JwtImpl jwt = (JwtImpl) decode(token); + Map map = new LinkedHashMap(jwt.header.parameters.map); + map.put("alg", jwt.header.parameters.alg); + if (jwt.header.parameters.typ!=null) { + map.put("typ", jwt.header.parameters.typ); + } + return map; + } public static Jwt encode(CharSequence content, Signer signer) { - JwtHeader header = JwtHeaderHelper.create(signer); + return encode(content, signer, Collections.emptyMap()); + } + + public static Jwt encode(CharSequence content, Signer signer, + Map headers) { + JwtHeader header = JwtHeaderHelper.create(signer, headers); byte[] claims = utf8Encode(content); - byte[] crypto = signer.sign(concat(b64UrlEncode(header.bytes()), PERIOD, b64UrlEncode(claims))); + byte[] crypto = signer + .sign(concat(b64UrlEncode(header.bytes()), PERIOD, b64UrlEncode(claims))); return new JwtImpl(header, claims, crypto); } } @@ -91,102 +113,93 @@ public static Jwt encode(CharSequence content, Signer signer) { * Handles the JSON parsing and serialization. */ class JwtHeaderHelper { - private static final JsonFactory f = new JsonFactory(); static JwtHeader create(String header) { byte[] bytes = b64UrlDecode(header); return new JwtHeader(bytes, parseParams(bytes)); } - - static JwtHeader create(Signer signer) { - HeaderParameters p = new HeaderParameters(sigAlg(signer.algorithm()), null, null); + static JwtHeader create(Signer signer, Map params) { + Map map = new LinkedHashMap(params); + map.put("alg", sigAlg(signer.algorithm())); + HeaderParameters p = new HeaderParameters(map); return new JwtHeader(serializeParams(p), p); } - static JwtHeader create(String alg, String enc, byte[] iv) { - HeaderParameters p = new HeaderParameters(alg, enc, utf8Decode(b64UrlEncode(iv))); - return new JwtHeader(serializeParams(p), p); + static HeaderParameters parseParams(byte[] header) { + Map map = parseMap(utf8Decode(header)); + return new HeaderParameters(map); } - static HeaderParameters parseParams(byte[] header) { - JsonParser jp = null; - try { - jp = f.createJsonParser(header); - String alg = null, enc = null, iv = null; - jp.nextToken(); - while (jp.nextToken() != JsonToken.END_OBJECT) { - String fieldname = jp.getCurrentName(); - jp.nextToken(); - if (!JsonToken.VALUE_STRING.equals(jp.getCurrentToken())) { - throw new IllegalArgumentException("Header fields must be strings"); - } - String value = jp.getText(); - if ("alg".equals(fieldname)) { - if (alg != null) { - throw new IllegalArgumentException("Duplicate 'alg' field"); - } - alg = value; - } else if ("enc".equals(fieldname)) { - if (enc != null) { - throw new IllegalArgumentException("Duplicate 'enc' field"); - } - enc = value; - } if ("iv".equals(fieldname)) { - if (iv != null) { - throw new IllegalArgumentException("Duplicate 'iv' field"); - } - iv = jp.nextToken().asString(); - } else if ("typ".equals(fieldname)) { - if (!"JWT".equalsIgnoreCase(value)) { - throw new IllegalArgumentException("typ is not \"JWT\""); - } - } + private static Map parseMap(String json) { + if (json != null) { + json = json.trim(); + if (json.startsWith("{")) { + return parseMapInternal(json); } - - return new HeaderParameters(alg, enc, iv); - } catch (IOException io) { - throw new RuntimeException(io); - } finally { - if (jp != null) { - try { - jp.close(); - } catch (IOException ignore) { - } + else if (json.equals("")) { + return new LinkedHashMap(); } } + throw new IllegalArgumentException("Invalid JSON (null)"); } - private static byte[] serializeParams(HeaderParameters params) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - JsonGenerator g = null; - - try { - g = f.createJsonGenerator(baos); - g.writeStartObject(); - g.writeStringField("alg", params.alg); - if (params.enc != null) { - g.writeStringField("enc", params.enc); + private static Map parseMapInternal(String json) { + Map map = new LinkedHashMap(); + json = trimLeadingCharacter(trimTrailingCharacter(json, '}'), '{'); + for (String pair : json.split(",")) { + String[] values = pair.split(":"); + String key = strip(values[0], '"'); + String value = null; + if (values.length > 0) { + value = strip(values[1], '"'); } - if (params.iv != null) { - g.writeStringField("iv", params.iv); + if (map.containsKey(key)) { + throw new IllegalArgumentException("Duplicate '" + key + "' field"); } - g.writeEndObject(); - g.flush(); + map.put(key, value); + } + return map; + } + + private static String strip(String string, char c) { + return trimLeadingCharacter(trimTrailingCharacter(string.trim(), c), c); + } - return baos.toByteArray(); + private static String trimTrailingCharacter(String string, char c) { + if (string.length() >= 0 && string.charAt(string.length() - 1) == c) { + return string.substring(0, string.length() - 1); } - catch (IOException io) { - throw new RuntimeException(io); - } finally { - if (g != null) { - try { - g.close(); - } catch (IOException ignore) { - } - } + return string; + } + + private static String trimLeadingCharacter(String string, char c) { + if (string.length() >= 0 && string.charAt(0) == c) { + return string.substring(1); } + return string; + } + private static byte[] serializeParams(HeaderParameters params) { + StringBuilder builder = new StringBuilder("{"); + + appendField(builder, "alg", params.alg); + if (params.typ != null) { + appendField(builder, "typ", params.typ); + } + for (Entry entry : params.map.entrySet()) { + appendField(builder, entry.getKey(), entry.getValue()); + } + builder.append("}"); + return utf8Encode(builder.toString()); + + } + + private static void appendField(StringBuilder builder, String name, String value) { + if (builder.length() > 1) { + builder.append(","); + } + builder.append("\"").append(name).append("\":\"").append(value).append("\""); } } @@ -196,6 +209,7 @@ private static byte[] serializeParams(HeaderParameters params) { */ class JwtHeader implements BinaryFormat { private final byte[] bytes; + final HeaderParameters parameters; /** @@ -207,6 +221,7 @@ class JwtHeader implements BinaryFormat { this.parameters = parameters; } + @Override public byte[] bytes() { return bytes; } @@ -219,33 +234,44 @@ public String toString() { class HeaderParameters { final String alg; - final String enc; - final String iv; + + final Map map; + + final String typ = "JWT"; HeaderParameters(String alg) { - this(alg, null, null); + this(new LinkedHashMap(Collections.singletonMap("alg", alg))); } - HeaderParameters(String alg, String enc, String iv) { + HeaderParameters(Map map) { + String alg = map.get("alg"), typ = map.get("typ"); + if (typ != null && !"JWT".equalsIgnoreCase(typ)) { + throw new IllegalArgumentException("typ is not \"JWT\""); + } + map.remove("alg"); + map.remove("typ"); + this.map = map; if (alg == null) { throw new IllegalArgumentException("alg is required"); } this.alg = alg; - this.enc = enc; - this.iv = iv; } } class JwtImpl implements Jwt { - private final JwtHeader header; + final JwtHeader header; + private final byte[] content; + private final byte[] crypto; + private String claims; /** * @param header the header, containing the JWS/JWE algorithm information. - * @param content the base64-decoded "claims" segment (may be encrypted, depending on header information). + * @param content the base64-decoded "claims" segment (may be encrypted, depending on + * header information). * @param crypto the base64-decoded "crypto" segment. */ JwtImpl(JwtHeader header, byte[] content, byte[] crypto) { @@ -260,33 +286,44 @@ class JwtImpl implements Jwt { * * @param verifier the signature verifier */ + @Override public void verifySignature(SignatureVerifier verifier) { verifier.verify(signingInput(), crypto); - } + } private byte[] signingInput() { - return concat(b64UrlEncode(header.bytes()), JwtHelper.PERIOD, b64UrlEncode(content)); + return concat(b64UrlEncode(header.bytes()), JwtHelper.PERIOD, + b64UrlEncode(content)); } - /** - * Allows retrieval of the full token. - * - * @return the encoded header, claims and crypto segments concatenated with "." characters - */ + /** + * Allows retrieval of the full token. + * + * @return the encoded header, claims and crypto segments concatenated with "." + * characters + */ + @Override public byte[] bytes() { - return concat(b64UrlEncode(header.bytes()), JwtHelper.PERIOD, b64UrlEncode(content), JwtHelper.PERIOD, b64UrlEncode(crypto)); + return concat(b64UrlEncode(header.bytes()), JwtHelper.PERIOD, + b64UrlEncode(content), JwtHelper.PERIOD, b64UrlEncode(crypto)); } + @Override public String getClaims() { return utf8Decode(content); } + @Override public String getEncoded() { return utf8Decode(bytes()); } + + public JwtHeader header() { + return this.header; + } @Override public String toString() { - return header + " " + claims + " ["+ crypto.length + " crypto bytes]"; + return header + " " + claims + " [" + crypto.length + " crypto bytes]"; } } diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/codec/Base64Codec.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/codec/Base64Codec.java index cc49155d3..9083bbcbb 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/codec/Base64Codec.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/codec/Base64Codec.java @@ -2,7 +2,7 @@ /** * Base64 encoder which is a reduced version of Robert Harder's public domain implementation (version 2.3.7). - * See http://iharder.net/base64 for more information. + * See https://iharder.sourceforge.net/current/java/base64/ for more information. * * @author Luke Taylor */ @@ -24,7 +24,7 @@ final class Base64Codec { /** * Encode using Base64-like encoding that is URL- and Filename-safe as described * in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. + * https://www.faqs.org/rfcs/rfc3548.html. * It is important to note that data encoded this way is not officially valid Base64, * or at the very least should not be called Base64 without also specifying that is * was encoded using the URL- and Filename-safe dialect. @@ -34,7 +34,7 @@ final class Base64Codec { /** * Encode using the special "ordered" dialect of Base64 described here: - * http://www.faqs.org/qa/rfcc-1940.html. + * https://www.faqs.org/qa/rfcc-1940.html. */ public final static int ORDERED = 32; @@ -115,7 +115,7 @@ final class Base64Codec { /** * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. + * https://www.faqs.org/rfcs/rfc3548.html. * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." */ private final static byte[] _URL_SAFE_ALPHABET = { @@ -179,7 +179,7 @@ final class Base64Codec { /** * I don't get the point of this technique, but someone requested it, * and it is described here: - * http://www.faqs.org/qa/rfcc-1940.html. + * https://www.faqs.org/qa/rfcc-1940.html. */ private final static byte[] _ORDERED_ALPHABET = { (byte)'-', diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/codec/Codecs.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/codec/Codecs.java index a654e52a5..28f31c527 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/codec/Codecs.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/codec/Codecs.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -20,8 +20,12 @@ /** * Functions for Hex, Base64 and Utf8 encoding/decoding * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Luke Taylor */ +@Deprecated public class Codecs { private static Charset UTF8 = Charset.forName("UTF-8"); diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/cipher/CipherMetadata.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/cipher/CipherMetadata.java index c8edfce77..d65e6c2eb 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/cipher/CipherMetadata.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/cipher/CipherMetadata.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -15,8 +15,12 @@ import org.springframework.security.jwt.AlgorithmMetadata; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Luke Taylor */ +@Deprecated public interface CipherMetadata extends AlgorithmMetadata { /** * @return Size of the key in bits. diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/EllipticCurveKeyHelper.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/EllipticCurveKeyHelper.java new file mode 100644 index 000000000..485d06a95 --- /dev/null +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/EllipticCurveKeyHelper.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.jwt.crypto.sign; + +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; +import org.bouncycastle.jce.spec.ECNamedCurveSpec; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; + +/** + * Utilities for Elliptic Curve keys. + * + * @author Michael Duergner + * @since 2.3 + */ +final class EllipticCurveKeyHelper { + + static ECPublicKey createPublicKey(final BigInteger x, final BigInteger y, final String curve) { + ECNamedCurveParameterSpec curveParameterSpec; + if ((curveParameterSpec = ECNamedCurveTable.getParameterSpec(curve)) == null) { + throw new IllegalArgumentException("Unsupported named curve: " + curve); + } + + ECParameterSpec parameterSpec = new ECNamedCurveSpec( + curveParameterSpec.getName(), + curveParameterSpec.getCurve(), + curveParameterSpec.getG(), + curveParameterSpec.getN()); + ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(x, y), parameterSpec); + + try { + return (ECPublicKey) KeyFactory.getInstance("EC").generatePublic(publicKeySpec); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/EllipticCurveSignatureHelper.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/EllipticCurveSignatureHelper.java new file mode 100644 index 000000000..8d6b911ed --- /dev/null +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/EllipticCurveSignatureHelper.java @@ -0,0 +1,163 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.jwt.crypto.sign; + +import java.security.GeneralSecurityException; + +/** + * Elliptic Curve Digital Signature Algorithm (ECDSA) utilities. + * + *

+ * Borrowed from com.nimbusds.jose.crypto.ECDSA. + */ +final class EllipticCurveSignatureHelper { + + /** + * Transcodes the JCA ASN.1/DER-encoded signature into the concatenated + * R + S format expected by ECDSA JWS. + * + * @param derSignature The ASN.1/DER-encoded. Must not be {@code null}. + * @param outputLength The expected length of the ECDSA JWS signature. + * + * @return The ECDSA JWS encoded signature. + * + * @throws GeneralSecurityException If the ASN.1/DER signature format is invalid. + */ + static byte[] transcodeSignatureToJWS(final byte[] derSignature, int outputLength) throws GeneralSecurityException { + + if (derSignature.length < 8 || derSignature[0] != 48) { + throw new GeneralSecurityException("Invalid ECDSA signature format"); + } + + int offset; + if (derSignature[1] > 0) { + offset = 2; + } else if (derSignature[1] == (byte) 0x81) { + offset = 3; + } else { + throw new GeneralSecurityException("Invalid ECDSA signature format"); + } + + byte rLength = derSignature[offset + 1]; + + int i; + for (i = rLength; (i > 0) && (derSignature[(offset + 2 + rLength) - i] == 0); i--) { + // do nothing + } + + byte sLength = derSignature[offset + 2 + rLength + 1]; + + int j; + for (j = sLength; (j > 0) && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0); j--) { + // do nothing + } + + int rawLen = Math.max(i, j); + rawLen = Math.max(rawLen, outputLength / 2); + + if ((derSignature[offset - 1] & 0xff) != derSignature.length - offset + || (derSignature[offset - 1] & 0xff) != 2 + rLength + 2 + sLength + || derSignature[offset] != 2 + || derSignature[offset + 2 + rLength] != 2) { + throw new GeneralSecurityException("Invalid ECDSA signature format"); + } + + final byte[] concatSignature = new byte[2 * rawLen]; + + System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i); + System.arraycopy(derSignature, (offset + 2 + rLength + 2 + sLength) - j, concatSignature, 2 * rawLen - j, j); + + return concatSignature; + } + + /** + * Transcodes the ECDSA JWS signature into ASN.1/DER format for use by + * the JCA verifier. + * + * @param jwsSignature The JWS signature, consisting of the + * concatenated R and S values. Must not be + * {@code null}. + * + * @return The ASN.1/DER encoded signature. + * + * @throws GeneralSecurityException If the ECDSA JWS signature format is invalid. + */ + static byte[] transcodeSignatureToDER(byte[] jwsSignature) throws GeneralSecurityException { + + // Adapted from org.apache.xml.security.algorithms.implementations.SignatureECDSA + + int rawLen = jwsSignature.length / 2; + + int i; + + for (i = rawLen; (i > 0) && (jwsSignature[rawLen - i] == 0); i--) { + // do nothing + } + + int j = i; + + if (jwsSignature[rawLen - i] < 0) { + j += 1; + } + + int k; + + for (k = rawLen; (k > 0) && (jwsSignature[2 * rawLen - k] == 0); k--) { + // do nothing + } + + int l = k; + + if (jwsSignature[2 * rawLen - k] < 0) { + l += 1; + } + + int len = 2 + j + 2 + l; + + if (len > 255) { + throw new GeneralSecurityException("Invalid ECDSA signature format"); + } + + int offset; + + final byte derSignature[]; + + if (len < 128) { + derSignature = new byte[2 + 2 + j + 2 + l]; + offset = 1; + } else { + derSignature = new byte[3 + 2 + j + 2 + l]; + derSignature[1] = (byte) 0x81; + offset = 2; + } + + derSignature[0] = 48; + derSignature[offset++] = (byte) len; + derSignature[offset++] = 2; + derSignature[offset++] = (byte) j; + + System.arraycopy(jwsSignature, rawLen - i, derSignature, (offset + j) - i, i); + + offset += j; + + derSignature[offset++] = 2; + derSignature[offset++] = (byte) l; + + System.arraycopy(jwsSignature, 2 * rawLen - k, derSignature, (offset + l) - k, k); + + return derSignature; + } +} \ No newline at end of file diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/EllipticCurveVerifier.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/EllipticCurveVerifier.java new file mode 100644 index 000000000..505d6dafb --- /dev/null +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/EllipticCurveVerifier.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.jwt.crypto.sign; + +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.Signature; +import java.security.interfaces.ECPublicKey; + +/** + * Verifies signatures using an Elliptic Curve public key. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Michael Duergner + * @since 2.3 + */ +@Deprecated +public class EllipticCurveVerifier implements SignatureVerifier { + private final ECPublicKey key; + private final String algorithm; + + public EllipticCurveVerifier(final BigInteger x, final BigInteger y, + final String curve, final String algorithm) { + this(EllipticCurveKeyHelper.createPublicKey(x, y, curve), algorithm); + } + + public EllipticCurveVerifier(final ECPublicKey key, final String algorithm) { + this.key = key; + this.algorithm = algorithm; + } + + @Override + public String algorithm() { + return this.algorithm; + } + + @Override + public void verify(byte[] content, byte[] sig) { + try { + Signature signature = Signature.getInstance(this.algorithm); + signature.initVerify(this.key); + signature.update(content); + + if (!signature.verify(EllipticCurveSignatureHelper.transcodeSignatureToDER(sig))) { + throw new InvalidSignatureException("EC Signature did not match content"); + } + } catch (GeneralSecurityException ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/InvalidSignatureException.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/InvalidSignatureException.java index 15095325c..6c7ef3e3c 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/InvalidSignatureException.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/InvalidSignatureException.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -13,8 +13,12 @@ package org.springframework.security.jwt.crypto.sign; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Luke Taylor */ +@Deprecated public class InvalidSignatureException extends RuntimeException { public InvalidSignatureException(String message) { super(message); diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/MacSigner.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/MacSigner.java index 71f75bbef..1aabb2f42 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/MacSigner.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/MacSigner.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -18,8 +18,12 @@ import javax.crypto.spec.SecretKeySpec; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Luke Taylor */ +@Deprecated public class MacSigner implements SignerVerifier { private static final String DEFAULT_ALGORITHM = "HMACSHA256"; diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/RsaKeyHelper.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/RsaKeyHelper.java index 1c03ba397..69a87874c 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/RsaKeyHelper.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/RsaKeyHelper.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -12,25 +12,23 @@ */ package org.springframework.security.jwt.crypto.sign; -import static org.springframework.security.jwt.codec.Codecs.b64Decode; -import static org.springframework.security.jwt.codec.Codecs.utf8Encode; +import org.bouncycastle.asn1.ASN1Sequence; import java.io.ByteArrayInputStream; import java.io.IOException; import java.math.BigInteger; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; +import java.security.*; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.security.interfaces.RSAPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.RSAPrivateCrtKeySpec; -import java.security.spec.RSAPublicKeySpec; +import java.security.spec.*; import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.bouncycastle.asn1.ASN1Primitive; -import org.bouncycastle.asn1.ASN1Sequence; +import static org.springframework.security.jwt.codec.Codecs.b64Decode; +import static org.springframework.security.jwt.codec.Codecs.utf8Encode; /** * Reads RSA key pairs using BC provider classes but without the @@ -43,41 +41,54 @@ class RsaKeyHelper { private static Pattern PEM_DATA = Pattern.compile("-----BEGIN (.*)-----(.*)-----END (.*)-----", Pattern.DOTALL); static KeyPair parseKeyPair(String pemData) { - Matcher m = PEM_DATA.matcher(pemData); + Matcher m = PEM_DATA.matcher(pemData.trim()); if (!m.matches()) { throw new IllegalArgumentException("String is not PEM encoded data"); } String type = m.group(1); + final byte[] content = b64Decode(utf8Encode(m.group(2))); - if (!type.equals("RSA PRIVATE KEY")) { - throw new IllegalArgumentException("Only private key data is currently supported"); - } - - String content = m.group(2); + PublicKey publicKey; + PrivateKey privateKey = null; try { - ASN1Sequence seq = ASN1Sequence.getInstance(ASN1Primitive.fromByteArray(b64Decode(utf8Encode(content)))); - if (seq.size() != 9) { - throw new IllegalArgumentException("Invalid RSA Key ASN1 sequence."); - } - org.bouncycastle.asn1.pkcs.RSAPrivateKey key = org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(seq); - RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()); - RSAPrivateCrtKeySpec privSpec = new RSAPrivateCrtKeySpec(key.getModulus(), key.getPublicExponent(), + KeyFactory fact = KeyFactory.getInstance("RSA"); + if (type.equals("RSA PRIVATE KEY")) { + ASN1Sequence seq = ASN1Sequence.getInstance(content); + if (seq.size() != 9) { + throw new IllegalArgumentException("Invalid RSA Private Key ASN1 sequence."); + } + org.bouncycastle.asn1.pkcs.RSAPrivateKey key = org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(seq); + RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()); + RSAPrivateCrtKeySpec privSpec = new RSAPrivateCrtKeySpec(key.getModulus(), key.getPublicExponent(), key.getPrivateExponent(), key.getPrime1(), key.getPrime2(), key.getExponent1(), key.getExponent2(), key.getCoefficient()); + publicKey = fact.generatePublic(pubSpec); + privateKey = fact.generatePrivate(privSpec); + } else if (type.equals("PUBLIC KEY")) { + KeySpec keySpec = new X509EncodedKeySpec(content); + publicKey = fact.generatePublic(keySpec); + } else if (type.equals("RSA PUBLIC KEY")) { + ASN1Sequence seq = ASN1Sequence.getInstance(content); + org.bouncycastle.asn1.pkcs.RSAPublicKey key = org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(seq); + RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()); + publicKey = fact.generatePublic(pubSpec); + } else if (type.equals("CERTIFICATE")) { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + Certificate certificate = certificateFactory.generateCertificate(new ByteArrayInputStream(content)); + publicKey = certificate.getPublicKey(); + } else { + throw new IllegalArgumentException(type + " is not a supported format"); + } - KeyFactory fact = KeyFactory.getInstance("RSA"); - - return new KeyPair( - fact.generatePublic(pubSpec), - fact.generatePrivate(privSpec)); + return new KeyPair(publicKey, privateKey); } - catch (IOException e) { + catch (InvalidKeySpecException e) { throw new RuntimeException(e); } - catch (InvalidKeySpecException e) { + catch (CertificateException e) { throw new RuntimeException(e); } catch (NoSuchAlgorithmException e) { @@ -85,7 +96,7 @@ static KeyPair parseKeyPair(String pemData) { } } - private static final Pattern SSH_PUB_KEY = Pattern.compile("ssh-(rsa|dsa) ([A-Za-z0-9/+]+=*) (.*)"); + private static final Pattern SSH_PUB_KEY = Pattern.compile("ssh-(rsa|dsa) ([A-Za-z0-9/+]+=*) ?(.*)"); static RSAPublicKey parsePublicKey(String key) { Matcher m = SSH_PUB_KEY.matcher(key); diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/RsaSigner.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/RsaSigner.java index bbd6b240a..6439fed67 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/RsaSigner.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/RsaSigner.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -23,8 +23,12 @@ * The key can be supplied directly, or as an SSH private key string (in * the standard format produced by ssh-keygen) * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Luke Taylor */ +@Deprecated public class RsaSigner implements Signer { static final String DEFAULT_ALGORITHM = "SHA256withRSA"; diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/RsaVerifier.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/RsaVerifier.java index faa562e1c..0e7cede4c 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/RsaVerifier.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/RsaVerifier.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -22,8 +22,12 @@ * The key can be supplied directly, or as an SSH public or private key string (in * the standard format produced by ssh-keygen). * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Luke Taylor */ +@Deprecated public class RsaVerifier implements SignatureVerifier { private final RSAPublicKey key; private final String algorithm; diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/SignatureVerifier.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/SignatureVerifier.java index 4a3371676..41253fbae 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/SignatureVerifier.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/SignatureVerifier.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -15,8 +15,12 @@ import org.springframework.security.jwt.AlgorithmMetadata; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Luke Taylor */ +@Deprecated public interface SignatureVerifier extends AlgorithmMetadata { void verify(byte[] content, byte[] signature); } diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/Signer.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/Signer.java index 2c5d98bda..ca675671f 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/Signer.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/Signer.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -15,8 +15,12 @@ import org.springframework.security.jwt.AlgorithmMetadata; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Luke Taylor */ +@Deprecated public interface Signer extends AlgorithmMetadata { byte[] sign(byte[] bytes); } diff --git a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/SignerVerifier.java b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/SignerVerifier.java index 00c52b713..d75dd5f7e 100644 --- a/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/SignerVerifier.java +++ b/spring-security-jwt/src/main/java/org/springframework/security/jwt/crypto/sign/SignerVerifier.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -13,7 +13,11 @@ package org.springframework.security.jwt.crypto.sign; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Luke Taylor */ +@Deprecated public interface SignerVerifier extends Signer, SignatureVerifier { } diff --git a/spring-security-jwt/src/test/java/org/springframework/security/jwt/JwtSpecData.java b/spring-security-jwt/src/test/java/org/springframework/security/jwt/JwtSpecData.java index a5fc71707..99139ee12 100644 --- a/spring-security-jwt/src/test/java/org/springframework/security/jwt/JwtSpecData.java +++ b/spring-security-jwt/src/test/java/org/springframework/security/jwt/JwtSpecData.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 diff --git a/spring-security-jwt/src/test/java/org/springframework/security/jwt/JwtTests.java b/spring-security-jwt/src/test/java/org/springframework/security/jwt/JwtTests.java index 0a191ca91..b3638bc11 100644 --- a/spring-security-jwt/src/test/java/org/springframework/security/jwt/JwtTests.java +++ b/spring-security-jwt/src/test/java/org/springframework/security/jwt/JwtTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -13,7 +13,13 @@ package org.springframework.security.jwt; import static org.junit.Assert.assertEquals; -import static org.springframework.security.jwt.JwtSpecData.*; +import static org.junit.Assert.assertTrue; +import static org.springframework.security.jwt.JwtSpecData.D; +import static org.springframework.security.jwt.JwtSpecData.E; +import static org.springframework.security.jwt.JwtSpecData.N; + +import java.util.Collections; +import java.util.Map; import org.junit.Test; import org.springframework.security.jwt.crypto.sign.InvalidSignatureException; @@ -28,13 +34,64 @@ public class JwtTests { /** * Sample from the JWT spec. */ - static final String JOE_CLAIM_SEGMENT = "{\"iss\":\"joe\",\r\n" + " \"exp\":1300819380,\r\n" + " \"http://example.com/is_root/":true}"; + static final String JOE_CLAIM_SEGMENT = "{\"iss\":\"joe\",\r\n" + " \"exp\":1300819380,\r\n" + + " \"https://example.com/is_root/":true}"; static final String JOE_HEADER_HMAC = "{\"typ\":\"JWT\",\r\n" + " \"alg\":\"HS256\"}"; - static final String JOE_HMAC_TOKEN = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9." + "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ." + "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; - static final String JOE_RSA_TOKEN = "eyJhbGciOiJSUzI1NiJ9." + "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ." + "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds" + "9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZR" + "mB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs9" + "8rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"; - static final String JOE_HEADER_RSA = "{\"alg\":\"RS256\"}"; + static final String JOE_HMAC_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." + + "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ." + + "SfgggA-oZk7ztlq1i8Uz5VhmPmustakoDa9wAf8uHyQ"; + static final String JOE_HMAC_TOKEN_NO_TYP = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9." + + "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ." + + "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; + static final String JOE_RSA_TOKEN = "eyJhbGciOiJSUzI1NiJ9." + + "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ." + + "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds" + + "9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZR" + + "mB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs9" + + "8rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"; static final MacSigner hmac = new MacSigner(JwtSpecData.HMAC_KEY); + @Test + public void defaultTokenContainsType() throws Exception { + Jwt token = JwtHelper.encode(JOE_CLAIM_SEGMENT, hmac); + assertTrue("Wrong header: " + token, token.toString().contains("\"alg\":\"HS256\",\"typ\":\"JWT\"")); + } + + @Test + public void inspectCustomHeaders() throws Exception { + Map headers = JwtHelper.headers( + JwtHelper.encode(JOE_CLAIM_SEGMENT, hmac, Collections.singletonMap("foo", "bar")).getEncoded()); + assertEquals("Wrong header: " + headers, "bar", headers.get("foo")); + assertEquals("Wrong header: " + headers, "HS256", headers.get("alg")); + assertEquals("Wrong header: " + headers, "JWT", headers.get("typ")); + } + + @Test + public void inspectHeaders() throws Exception { + Map headers = JwtHelper.headers(JOE_RSA_TOKEN); + assertEquals("Wrong header: " + headers, "RS256", headers.get("alg")); + assertEquals("Wrong header: " + headers, "JWT", headers.get("typ")); + } + + @Test + public void roundTripCustomHeaders() throws Exception { + Jwt token = JwtHelper + .decode(JwtHelper.encode(JOE_CLAIM_SEGMENT, hmac, Collections.singletonMap("foo", "bar")).getEncoded()); + assertTrue("Wrong header: " + token, token.toString().contains("\"foo\":\"bar\"")); + } + + @Test + public void roundTripClaims() throws Exception { + Jwt token = JwtHelper.decode(JwtHelper.encode(JOE_CLAIM_SEGMENT, hmac).getEncoded()); + assertTrue("Wrong header: " + token, token.toString().contains("\"alg\":\"HS256\",\"typ\":\"JWT\"")); + } + + @Test + public void tokenWithNoTypeCanBeDecoded() throws Exception { + Jwt token = JwtHelper.decode(JOE_HMAC_TOKEN_NO_TYP); + assertEquals(JOE_HMAC_TOKEN_NO_TYP, token.getEncoded()); + } + @Test public void tokenBytesCreateSameToken() throws Exception { Jwt token = JwtHelper.decode(JOE_HMAC_TOKEN); @@ -44,7 +101,8 @@ public void tokenBytesCreateSameToken() throws Exception { @Test public void expectedClaimsValueIsReturned() { - assertEquals(JOE_CLAIM_SEGMENT, JwtHelper.decode(JOE_HMAC_TOKEN).getClaims()); + Jwt token = JwtHelper.encode(JOE_CLAIM_SEGMENT, hmac); + assertEquals(JOE_CLAIM_SEGMENT, JwtHelper.decode(token.getEncoded()).getClaims()); } @Test @@ -52,7 +110,7 @@ public void hmacSignedTokenParsesAndVerifies() { JwtHelper.decode(JOE_HMAC_TOKEN).verifySignature(hmac); } - @Test(expected=InvalidSignatureException.class) + @Test(expected = InvalidSignatureException.class) public void invalidHmacSignatureRaisesException() { JwtHelper.decode(JOE_HMAC_TOKEN).verifySignature(new MacSigner("differentkey".getBytes())); } @@ -66,13 +124,13 @@ public void tokenMissingSignatureIsRejected() { public void hmacVerificationIsInverseOfSigning() { Jwt jwt = JwtHelper.encode(JOE_CLAIM_SEGMENT, hmac); jwt.verifySignature(hmac); - assertEquals (JOE_CLAIM_SEGMENT, jwt.getClaims()); + assertEquals(JOE_CLAIM_SEGMENT, jwt.getClaims()); } @Test public void rsaSignedTokenParsesAndVerifies() { - Jwt jwt = JwtHelper.decode(JOE_RSA_TOKEN); - jwt.verifySignature(new RsaVerifier(N, E)); + Jwt jwt = JwtHelper.encode(JOE_CLAIM_SEGMENT, new RsaSigner(N, E)); + jwt.verifySignature(new RsaVerifier(N, D)); assertEquals(JOE_CLAIM_SEGMENT, jwt.getClaims()); } @@ -87,5 +145,3 @@ public void rsaVerificationIsInverseOfSigning() { jwt.verifySignature(new RsaVerifier(N, D)); } } - - diff --git a/spring-security-jwt/src/test/java/org/springframework/security/jwt/RubyJwtIntegrationTests.java b/spring-security-jwt/src/test/java/org/springframework/security/jwt/RubyJwtIntegrationTests.java index 33821d3a3..6aa3b406a 100644 --- a/spring-security-jwt/src/test/java/org/springframework/security/jwt/RubyJwtIntegrationTests.java +++ b/spring-security-jwt/src/test/java/org/springframework/security/jwt/RubyJwtIntegrationTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -28,19 +28,20 @@ /** * Tests for compatibility with Ruby's JWT Gem. * - * Requires a local JRuby installation and maven must be run with: + * Requires a local JRuby installation with jruby.home as a system property. Using RVM + * sets this up automatically (e.g. "rvm use jruby-1.7.12"), or alternatively Maven can be + * run with: + * *

  * mvn -DargLine="-Djruby.home=${JRUBY_HOME}" test
  * 
* * See https://github.com/jruby/jruby/wiki/JavaIntegration for issues with gem loading * - * You also need to: - * jruby -S gem install jwt - * jruby -S gem install jruby-openssl + * You also need to: jruby -S gem install jwt jruby -S gem install jruby-openssl * - * for the tests to work. If the environment isn't set up correctly or jruby.home isn't set, - * they will be skipped. + * for the tests to work. If the environment isn't set up correctly or jruby.home isn't + * set, they will be skipped. * * @author Luke Taylor */ @@ -55,8 +56,8 @@ public class RubyJwtIntegrationTests { @BeforeClass public static void setUp() throws Exception { -// System.setProperty("jruby.home", "/Users/luke/Work/tools/jruby-1.6.5.1"); -// System.setProperty("jruby.lib", "/Users/luke/Work/tools/jruby-1.6.5.1/lib"); + // System.setProperty("jruby.home", "/Users/luke/Work/tools/jruby-1.6.5.1"); + // System.setProperty("jruby.lib", "/Users/luke/Work/tools/jruby-1.6.5.1/lib"); } @Test @@ -65,11 +66,9 @@ public void rubyCanDecodeHmacSignedToken() throws Exception { ScriptingContainer container = new ScriptingContainer(); container.put("@token", jwt.getEncoded()); container.put("@claims", ""); - String script = - "require \"rubygems\"\n" + - "require \"jwt\"\n" + - "@claims = JWT.decode(@token, \"secret\", \"HS256\").to_json\n" + - "puts @claims"; + String script = "require \"jwt\"\n" + + "@claims = JWT.decode(@token, \"secret\", \"HS256\")[0].to_json\n" + + "puts @claims"; container.runScriptlet(script); assertEquals(TEST_CLAIMS, container.get("@claims")); } @@ -78,11 +77,9 @@ public void rubyCanDecodeHmacSignedToken() throws Exception { public void canDecodeRubyHmacSignedToken() throws Exception { ScriptingContainer container = new ScriptingContainer(); container.put("@token", "xxx"); - String script = - "require \"rubygems\"\n" + - "require \"jwt\"\n" + - "@token = JWT.encode({\"some\" => \"payload\"}, \"secret\", \"HS256\")\n" + - "puts @token"; + String script = "require \"jwt\"\n" + + "@token = JWT.encode({\"some\" => \"payload\"}, \"secret\", \"HS256\")\n" + + "puts @token"; container.runScriptlet(script); String token = (String) container.get("@token"); Jwt jwt = JwtHelper.decodeAndVerify(token, hmac); @@ -99,22 +96,23 @@ public JRubyJwtInstalled() { setupOk = false; setupOkSet = true; try { - String script = - "require \"rubygems\"\n" + - "require \"jwt\"\n" + - "require \"bouncy-castle-java\"\n" + - "require \"openssl\""; + String script = "require \"jwt\"\n" + + "require \"bouncy-castle-java\"\n" + "require \"openssl\""; ScriptingContainer container = new ScriptingContainer(); container.runScriptlet(script); setupOk = true; } catch (Exception e) { - System.out.println("jruby.home not set or JWT gem not available. JWT ruby integration tests will be skipped" + e.getMessage()); + System.out.println( + "jruby.home not set or JWT gem not available. JWT ruby integration tests will be skipped" + + e.getMessage()); } } } - public Statement apply(final Statement base, FrameworkMethod method, Object target) { + @Override + public Statement apply(final Statement base, FrameworkMethod method, + Object target) { return new Statement() { @Override public void evaluate() throws Throwable { diff --git a/spring-security-jwt/src/test/java/org/springframework/security/jwt/crypto/cipher/RsaTestKeyData.java b/spring-security-jwt/src/test/java/org/springframework/security/jwt/crypto/cipher/RsaTestKeyData.java index cd46dc505..5efbfe88d 100644 --- a/spring-security-jwt/src/test/java/org/springframework/security/jwt/crypto/cipher/RsaTestKeyData.java +++ b/spring-security-jwt/src/test/java/org/springframework/security/jwt/crypto/cipher/RsaTestKeyData.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -19,56 +19,128 @@ */ public class RsaTestKeyData { static final byte[] nBytes; + static final byte[] dBytes; static { - int[] nInts = new int[] {161, 248, 22, 10, 226, 227, 201, 180, 101, 206, 141, 45, 101, 98, 99, 54, 43, 146, 125, 190, 41, 225, 240, 36, 119, 252, 22, 37, 204, 144, 161, 54, 227, 139, 217, 52, 151, 197, 182, 234, 99, 221, 119, 17, 230, 124, 116, 41, 249, 86, 176, 251, 138, 143, 8, 154, 220, 75, 105, 137, 60, 193, 51, 63, 83, 237, 208, 25, 184, 119, 132, 37, 47, 236, 145, 79, 228, 133, 119, 105, 89, 75, 234, 66, 128, 211, 44, 15, 85, 191, 98, 148, 79, 19, 3, 150, 188, 110, 155, 223, 110, 189, 210, 189, 163, 103, 142, 236, 160, 198, 104, 247, 1, 179, 141, 191, 251, 56, 200, 52, 44, 226, 254, 109, 39, 250, 222, 74, 90, 72, 116, 151, 157, 212, 185, 207, 154, 222, 196, 199, 91, 5, 133, 44, 44, 15, 94, 248, 165, 193, 117, 3, 146, 249, 68, 232, 237, 100, 193, 16, 198, 182, 71, 96, 154, 164, 120, 58, 235, 156, 108, 154, 215, 85, 49, 48, 80, 99, 139, 131, 102, 92, 111, 111, 122, 130, 163, 150, 112, 42, 31, 100, 27, 130, 211, 235, 242, 57, 34, 25, 73, 31, 182, 134, 135, 44, 87, 22, 245, 10, 248, 53, 141, 154, 139, 157, 23, 195, 64, 114, 143, 127, 135, 216, 154, 24, 216, 252, 171, 103, 173, 132, 89, 12, 46, 207, 117, 147, 57, 54, 60, 7, 3, 77, 111, 96, 111, 158, 33, 224, 84, 86, 202, 229, 233, 161}; - int[] dInts = new int[] {18, 174, 113, 164, 105, 205, 10, 43, 195, 126, 82, 108, 69, 0, 87, 31, 29, 97, 117, 29, 100, 233, 73, 112, 123, 98, 89, 15, 157, 11, 165, 124, 150, 60, 64, 30, 63, 207, 47, 44, 211, 189, 236, 136, 229, 3, 191, 198, 67, 155, 11, 40, 200, 47, 125, 55, 151, 103, 31, 82, 19, 238, 216, 193, 90, 37, 216, 213, 206, 160, 2, 94, 227, 171, 46, 139, 127, 121, 33, 111, 198, 59, 234, 86, 39, 83, 180, 6, 68, 198, 161, 81, 39, 217, 178, 149, 69, 64, 160, 187, 225, 163, 5, 86, 152, 45, 78, 159, 222, 95, 100, 37, 241, 77, 75, 113, 52, 65, 181, 93, 199, 59, 155, 74, 237, 204, 146, 172, 227, 146, 126, 55, 245, 125, 12, 253, 94, 117, 129, 250, 81, 44, 143, 73, 97, 169, 235, 11, 128, 248, 168, 7, 70, 114, 138, 85, 255, 70, 71, 31, 52, 37, 6, 59, 157, 83, 100, 47, 94, 222, 30, 132, 214, 19, 8, 26, 250, 92, 34, 208, 81, 40, 91, 214, 59, 148, 59, 86, 93, 137, 138, 5, 104, 84, 19, 229, 60, 60, 108, 101, 37, 255, 31, 227, 78, 61, 220, 112, 240, 213, 100, 80, 253, 164, 139, 161, 46, 16, 78, 157, 235, 159, 184, 24, 129, 225, 196, 189, 242, 93, 146, 71, 244, 80, 200, 101, 146, 121, 104, 231, 115, 52, 244, 65, 79, 117, 167, 80, 225, 57, 84, 110, 58, 138, 115, 157}; + int[] nInts = new int[] { 161, 248, 22, 10, 226, 227, 201, 180, 101, 206, 141, 45, 101, 98, 99, 54, 43, 146, + 125, 190, 41, 225, 240, 36, 119, 252, 22, 37, 204, 144, 161, 54, 227, 139, 217, 52, 151, 197, 182, 234, + 99, 221, 119, 17, 230, 124, 116, 41, 249, 86, 176, 251, 138, 143, 8, 154, 220, 75, 105, 137, 60, 193, + 51, 63, 83, 237, 208, 25, 184, 119, 132, 37, 47, 236, 145, 79, 228, 133, 119, 105, 89, 75, 234, 66, + 128, 211, 44, 15, 85, 191, 98, 148, 79, 19, 3, 150, 188, 110, 155, 223, 110, 189, 210, 189, 163, 103, + 142, 236, 160, 198, 104, 247, 1, 179, 141, 191, 251, 56, 200, 52, 44, 226, 254, 109, 39, 250, 222, 74, + 90, 72, 116, 151, 157, 212, 185, 207, 154, 222, 196, 199, 91, 5, 133, 44, 44, 15, 94, 248, 165, 193, + 117, 3, 146, 249, 68, 232, 237, 100, 193, 16, 198, 182, 71, 96, 154, 164, 120, 58, 235, 156, 108, 154, + 215, 85, 49, 48, 80, 99, 139, 131, 102, 92, 111, 111, 122, 130, 163, 150, 112, 42, 31, 100, 27, 130, + 211, 235, 242, 57, 34, 25, 73, 31, 182, 134, 135, 44, 87, 22, 245, 10, 248, 53, 141, 154, 139, 157, 23, + 195, 64, 114, 143, 127, 135, 216, 154, 24, 216, 252, 171, 103, 173, 132, 89, 12, 46, 207, 117, 147, 57, + 54, 60, 7, 3, 77, 111, 96, 111, 158, 33, 224, 84, 86, 202, 229, 233, 161 }; + int[] dInts = new int[] { 18, 174, 113, 164, 105, 205, 10, 43, 195, 126, 82, 108, 69, 0, 87, 31, 29, 97, 117, + 29, 100, 233, 73, 112, 123, 98, 89, 15, 157, 11, 165, 124, 150, 60, 64, 30, 63, 207, 47, 44, 211, 189, + 236, 136, 229, 3, 191, 198, 67, 155, 11, 40, 200, 47, 125, 55, 151, 103, 31, 82, 19, 238, 216, 193, 90, + 37, 216, 213, 206, 160, 2, 94, 227, 171, 46, 139, 127, 121, 33, 111, 198, 59, 234, 86, 39, 83, 180, 6, + 68, 198, 161, 81, 39, 217, 178, 149, 69, 64, 160, 187, 225, 163, 5, 86, 152, 45, 78, 159, 222, 95, 100, + 37, 241, 77, 75, 113, 52, 65, 181, 93, 199, 59, 155, 74, 237, 204, 146, 172, 227, 146, 126, 55, 245, + 125, 12, 253, 94, 117, 129, 250, 81, 44, 143, 73, 97, 169, 235, 11, 128, 248, 168, 7, 70, 114, 138, 85, + 255, 70, 71, 31, 52, 37, 6, 59, 157, 83, 100, 47, 94, 222, 30, 132, 214, 19, 8, 26, 250, 92, 34, 208, + 81, 40, 91, 214, 59, 148, 59, 86, 93, 137, 138, 5, 104, 84, 19, 229, 60, 60, 108, 101, 37, 255, 31, + 227, 78, 61, 220, 112, 240, 213, 100, 80, 253, 164, 139, 161, 46, 16, 78, 157, 235, 159, 184, 24, 129, + 225, 196, 189, 242, 93, 146, 71, 244, 80, 200, 101, 146, 121, 104, 231, 115, 52, 244, 65, 79, 117, 167, + 80, 225, 57, 84, 110, 58, 138, 115, 157 }; nBytes = new byte[nInts.length]; dBytes = new byte[dInts.length]; - for (int i=0; i < nInts.length; i++) - nBytes[i] = (byte)nInts[i]; - for (int i=0; i < dInts.length; i++) - dBytes[i] = (byte)dInts[i]; + for (int i = 0; i < nInts.length; i++) + nBytes[i] = (byte) nInts[i]; + for (int i = 0; i < dInts.length; i++) + dBytes[i] = (byte) dInts[i]; } public final static BigInteger N = new BigInteger(1, nBytes); - public final static BigInteger E = new BigInteger(1, new byte[]{1,0,1}); + + public final static BigInteger E = new BigInteger(1, new byte[] { 1, 0, 1 }); + public final static BigInteger D = new BigInteger(1, dBytes); - public final static String SSH_PRIVATE_KEY_STRING = "-----BEGIN RSA PRIVATE KEY-----\n" + - " MIIEowIBAAKCAQEAwARN4S7Z0asSEj61+SIvtUUuHopd/ffne1CbaHXNxj/cI4rY\n " + - "0k5ELZ2SGCFVgmx9XADJJhYoQImO+vMxFAqbWxyO45B1rZR1q0ChEFWLGPmNB+fY \n" + - "8TrFHIjJb873s0d2FTYDOwst6HdKPjXkLdgGHO4K1fLnO1cQHKGglBKvc4ZSVniU\n" + - "OJ1EdKZHxGnkVjps1hP98AvQx6EpmKExewd4MMj77gRYAeSo0pPhugLrmy5DLPox\n" + - " SUGSZEHCPlCfOfTAt9NuE4YwbpCwDfDmQb+9neq7Q0PwmtP6jFrV4VZ3ir2cWYOT\n" + - "F6FcL7ZIncG3aCvXxp8pUQ7NPimYd70dEPuu1QIDAQABAoIBAFbcG5a3qNTNu/kA\n" + - "4TR3oHkxeDFcijQuhkokJojUcWcy0BRL5NUNjo3L76B2w8Wh6ftKZ7OQ5lh7YXBn\n" + - " vlXAjpJiksiiOnlw5OG49KL871U23fMrj9lfqnbD8ctgJnC07NeffUqiPfwgqjcG\n" + - " DdgnFmzTyZcKsEsJkUJCYu9YnIF3AwEwOxlstdE1YXmjbXmbXlfqUynWEaZUxnAW\n" + - "/jLqHuQ9Dq9x8/i3vY+z8vzGVWY4ND8HSOMxLoQnoeA4Z0IWveaulVP0sZxbZTPo\n" + - "PvZu8yDFgdS6tCTRBw/WyzQeIcaktgoF1Mfv+YgiP7/GxiuFocyR21lykvWasvCb\n" + - "kWUadoECgYEA4Jdc2crrm30L+jgGzK7T4OMV2E1IvxloV1q3RBN8W5Gf0ba6ZtFh\n" + - "aW3008sUyZDPoQfpeq4AlwaYZDlOp7kK2Ur15qQ/w4AjnUjGAFFVE3hcBWkj0+3z\n" + - "1sQQXe87LIGG9iaTct+bhbav7cLnZKBKHJNkhgsk/XEHvub5kIvXWjECgYEA2t68\n" + - "tCUgCsU/v311Jc6HNJ5hB0FzGBEx/ic1QjQF2sicP0yCO0qPHCqHGCazzyM1P9So\n" + - "eDRE/bzWejk2v/UgaN9P/1+TeQdj9VR/9wfwlWPYtYWZZRgvHl6HIXKJFRON5Mfh\n" + - "KaaHqhY0hRS4u5e3VpA7wtUNMgRQdUgr91CW0eUCgYA6ILLVY6GrMqgg8NNBspYA\n" + - "BIYo34fOfgL1aPM52Vk8UeptWr/P0K1Hnj/ZeRw+Nw6l/Og+6j4Y2Ioklnh3DHt0\n" + - "VeRi92vRa57MHIOynVpJmcMnW1j8hv+vPDuINFy6XiPSHZXYC2uzJd9OyD0fXCUS\n" + - "VEuWLdg7CEAa9qjs8mSgYQKBgHmfgHkSkEWr6oq8apbBt3xT7lMb2ZssIv26R+ws\n" + - "AHzdMYYzO8M64V+jekK/bvfR9ssrnxp84UGm6AAvPu9YhdQBE/Ey6T4+DxvLAvkB\n" + - "Hn3FaC0mumDlGXnkyW6auPZPUXAqakK82XJ4uGKjayxDWIvvxmW0AosivpsNqfDa\n" + - "hZTJAoGBAL6qCOit9K/mHIBFcQuwBl2FbQ8TVosnim1ey1iVOkLVqoMGeNxIQooy\n" + - "croHabgtkH1IatCZyvTqgq4uCeya4kSRz0kzS86UehMr14ZXcoK+fS+JaAJrbce6\n" + - " 1iX3/15AzMUG3DOrUHpY/Ye/9MDMvBOtLNRNzthGY0rhUGP53j9D\n" + - "-----END RSA PRIVATE KEY-----"; - - public static final String SSH_PUBLIC_KEY_STRING = "ssh-rsa " + - "AAAAB3NzaC1yc2EAAAADAQABAAABAQDABE3hLtnRqxISPrX5Ii+1RS4eil399+d7UJtodc3GP9wjitjSTkQtnZIYIVWCbH1cAMkmFi" + - "hAiY768zEUCptbHI7jkHWtlHWrQKEQVYsY+Y0H59jxOsUciMlvzvezR3YVNgM7Cy3od0o+NeQt2AYc7grV8uc7VxAcoaCUEq9zhlJW" + - "eJQ4nUR0pkfEaeRWOmzWE/3wC9DHoSmYoTF7B3gwyPvuBFgB5KjSk+G6AuubLkMs+jFJQZJkQcI+UJ859MC3024ThjBukLAN8OZBv7" + - "2d6rtDQ/Ca0/qMWtXhVneKvZxZg5MXoVwvtkidwbdoK9fGnylRDs0+KZh3vR0Q+67V blah@blah.local"; + public final static String SSH_PRIVATE_KEY_STRING = "-----BEGIN RSA PRIVATE KEY-----\n" + + " MIIEowIBAAKCAQEAwARN4S7Z0asSEj61+SIvtUUuHopd/ffne1CbaHXNxj/cI4rY\n " + + "0k5ELZ2SGCFVgmx9XADJJhYoQImO+vMxFAqbWxyO45B1rZR1q0ChEFWLGPmNB+fY \n" + + "8TrFHIjJb873s0d2FTYDOwst6HdKPjXkLdgGHO4K1fLnO1cQHKGglBKvc4ZSVniU\n" + + "OJ1EdKZHxGnkVjps1hP98AvQx6EpmKExewd4MMj77gRYAeSo0pPhugLrmy5DLPox\n" + + " SUGSZEHCPlCfOfTAt9NuE4YwbpCwDfDmQb+9neq7Q0PwmtP6jFrV4VZ3ir2cWYOT\n" + + "F6FcL7ZIncG3aCvXxp8pUQ7NPimYd70dEPuu1QIDAQABAoIBAFbcG5a3qNTNu/kA\n" + + "4TR3oHkxeDFcijQuhkokJojUcWcy0BRL5NUNjo3L76B2w8Wh6ftKZ7OQ5lh7YXBn\n" + + " vlXAjpJiksiiOnlw5OG49KL871U23fMrj9lfqnbD8ctgJnC07NeffUqiPfwgqjcG\n" + + " DdgnFmzTyZcKsEsJkUJCYu9YnIF3AwEwOxlstdE1YXmjbXmbXlfqUynWEaZUxnAW\n" + + "/jLqHuQ9Dq9x8/i3vY+z8vzGVWY4ND8HSOMxLoQnoeA4Z0IWveaulVP0sZxbZTPo\n" + + "PvZu8yDFgdS6tCTRBw/WyzQeIcaktgoF1Mfv+YgiP7/GxiuFocyR21lykvWasvCb\n" + + "kWUadoECgYEA4Jdc2crrm30L+jgGzK7T4OMV2E1IvxloV1q3RBN8W5Gf0ba6ZtFh\n" + + "aW3008sUyZDPoQfpeq4AlwaYZDlOp7kK2Ur15qQ/w4AjnUjGAFFVE3hcBWkj0+3z\n" + + "1sQQXe87LIGG9iaTct+bhbav7cLnZKBKHJNkhgsk/XEHvub5kIvXWjECgYEA2t68\n" + + "tCUgCsU/v311Jc6HNJ5hB0FzGBEx/ic1QjQF2sicP0yCO0qPHCqHGCazzyM1P9So\n" + + "eDRE/bzWejk2v/UgaN9P/1+TeQdj9VR/9wfwlWPYtYWZZRgvHl6HIXKJFRON5Mfh\n" + + "KaaHqhY0hRS4u5e3VpA7wtUNMgRQdUgr91CW0eUCgYA6ILLVY6GrMqgg8NNBspYA\n" + + "BIYo34fOfgL1aPM52Vk8UeptWr/P0K1Hnj/ZeRw+Nw6l/Og+6j4Y2Ioklnh3DHt0\n" + + "VeRi92vRa57MHIOynVpJmcMnW1j8hv+vPDuINFy6XiPSHZXYC2uzJd9OyD0fXCUS\n" + + "VEuWLdg7CEAa9qjs8mSgYQKBgHmfgHkSkEWr6oq8apbBt3xT7lMb2ZssIv26R+ws\n" + + "AHzdMYYzO8M64V+jekK/bvfR9ssrnxp84UGm6AAvPu9YhdQBE/Ey6T4+DxvLAvkB\n" + + "Hn3FaC0mumDlGXnkyW6auPZPUXAqakK82XJ4uGKjayxDWIvvxmW0AosivpsNqfDa\n" + + "hZTJAoGBAL6qCOit9K/mHIBFcQuwBl2FbQ8TVosnim1ey1iVOkLVqoMGeNxIQooy\n" + + "croHabgtkH1IatCZyvTqgq4uCeya4kSRz0kzS86UehMr14ZXcoK+fS+JaAJrbce6\n" + + " 1iX3/15AzMUG3DOrUHpY/Ye/9MDMvBOtLNRNzthGY0rhUGP53j9D\n" + "-----END RSA PRIVATE KEY-----"; + + public final static String SSH_PRIVATE_KEY_STRING_WITH_WHITESPACE = SSH_PRIVATE_KEY_STRING + " "; + + public static final String SSH_PUBLIC_KEY_STRING = "ssh-rsa " + + "AAAAB3NzaC1yc2EAAAADAQABAAABAQDABE3hLtnRqxISPrX5Ii+1RS4eil399+d7UJtodc3GP9wjitjSTkQtnZIYIVWCbH1cAMkmFi" + + "hAiY768zEUCptbHI7jkHWtlHWrQKEQVYsY+Y0H59jxOsUciMlvzvezR3YVNgM7Cy3od0o+NeQt2AYc7grV8uc7VxAcoaCUEq9zhlJW" + + "eJQ4nUR0pkfEaeRWOmzWE/3wC9DHoSmYoTF7B3gwyPvuBFgB5KjSk+G6AuubLkMs+jFJQZJkQcI+UJ859MC3024ThjBukLAN8OZBv7" + + "2d6rtDQ/Ca0/qMWtXhVneKvZxZg5MXoVwvtkidwbdoK9fGnylRDs0+KZh3vR0Q+67V blah@blah.local"; + + public static final String SSH_PUBLIC_KEY_STRING_WITHOUT_COMMENT = "ssh-rsa " + + "AAAAB3NzaC1yc2EAAAADAQABAAABAQDABE3hLtnRqxISPrX5Ii+1RS4eil399+d7UJtodc3GP9wjitjSTkQtnZIYIVWCbH1cAMkmFi" + + "hAiY768zEUCptbHI7jkHWtlHWrQKEQVYsY+Y0H59jxOsUciMlvzvezR3YVNgM7Cy3od0o+NeQt2AYc7grV8uc7VxAcoaCUEq9zhlJW" + + "eJQ4nUR0pkfEaeRWOmzWE/3wC9DHoSmYoTF7B3gwyPvuBFgB5KjSk+G6AuubLkMs+jFJQZJkQcI+UJ859MC3024ThjBukLAN8OZBv7" + + "2d6rtDQ/Ca0/qMWtXhVneKvZxZg5MXoVwvtkidwbdoK9fGnylRDs0+KZh3vR0Q+67V"; + + + public static final String SSH_PUBLIC_KEY_OPENSSL_PEM_STRING = "-----BEGIN PUBLIC KEY-----\n" + + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwARN4S7Z0asSEj61+SIv\n" + + "tUUuHopd/ffne1CbaHXNxj/cI4rY0k5ELZ2SGCFVgmx9XADJJhYoQImO+vMxFAqb\n" + + "WxyO45B1rZR1q0ChEFWLGPmNB+fY8TrFHIjJb873s0d2FTYDOwst6HdKPjXkLdgG\n" + + "HO4K1fLnO1cQHKGglBKvc4ZSVniUOJ1EdKZHxGnkVjps1hP98AvQx6EpmKExewd4\n" + + "MMj77gRYAeSo0pPhugLrmy5DLPoxSUGSZEHCPlCfOfTAt9NuE4YwbpCwDfDmQb+9\n" + + "neq7Q0PwmtP6jFrV4VZ3ir2cWYOTF6FcL7ZIncG3aCvXxp8pUQ7NPimYd70dEPuu\n" + + "1QIDAQAB\n" + + "-----END PUBLIC KEY-----"; + + public static final String SSH_PUBLIC_KEY_PEM_STRING = "-----BEGIN RSA PUBLIC KEY-----\n" + + "MIIBCgKCAQEAwARN4S7Z0asSEj61+SIvtUUuHopd/ffne1CbaHXNxj/cI4rY0k5E\n" + + "LZ2SGCFVgmx9XADJJhYoQImO+vMxFAqbWxyO45B1rZR1q0ChEFWLGPmNB+fY8TrF\n" + + "HIjJb873s0d2FTYDOwst6HdKPjXkLdgGHO4K1fLnO1cQHKGglBKvc4ZSVniUOJ1E\n" + + "dKZHxGnkVjps1hP98AvQx6EpmKExewd4MMj77gRYAeSo0pPhugLrmy5DLPoxSUGS\n" + + "ZEHCPlCfOfTAt9NuE4YwbpCwDfDmQb+9neq7Q0PwmtP6jFrV4VZ3ir2cWYOTF6Fc\n" + + "L7ZIncG3aCvXxp8pUQ7NPimYd70dEPuu1QIDAQAB\n" + + "-----END RSA PUBLIC KEY-----"; + + public static final String SSH_X509_CERTIFICATE_PEM_STRING = "-----BEGIN CERTIFICATE-----\n" + + "MIIDHDCCAgSgAwIBAgIJAK+wnYpjtdVFMA0GCSqGSIb3DQEBCwUAMCMxITAfBgNV\n" + + "BAMMGHNwcmluZy1zZWN1cml0eS1qd3QtdGVzdDAeFw0xODA0MTcwOTQ4MzVaFw0x\n" + + "ODA1MTcwOTQ4MzVaMCMxITAfBgNVBAMMGHNwcmluZy1zZWN1cml0eS1qd3QtdGVz\n" + + "dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMAETeEu2dGrEhI+tfki\n" + + "L7VFLh6KXf3353tQm2h1zcY/3COK2NJORC2dkhghVYJsfVwAySYWKECJjvrzMRQK\n" + + "m1scjuOQda2UdatAoRBVixj5jQfn2PE6xRyIyW/O97NHdhU2AzsLLeh3Sj415C3Y\n" + + "BhzuCtXy5ztXEByhoJQSr3OGUlZ4lDidRHSmR8Rp5FY6bNYT/fAL0MehKZihMXsH\n" + + "eDDI++4EWAHkqNKT4boC65suQyz6MUlBkmRBwj5Qnzn0wLfTbhOGMG6QsA3w5kG/\n" + + "vZ3qu0ND8JrT+oxa1eFWd4q9nFmDkxehXC+2SJ3Bt2gr18afKVEOzT4pmHe9HRD7\n" + + "rtUCAwEAAaNTMFEwHQYDVR0OBBYEFPM7mHoBTz7Bgyblen9oSqd6gCVTMB8GA1Ud\n" + + "IwQYMBaAFPM7mHoBTz7Bgyblen9oSqd6gCVTMA8GA1UdEwEB/wQFMAMBAf8wDQYJ\n" + + "KoZIhvcNAQELBQADggEBAGfx6+D8YpYVHYbB9mdUDVmFKEq3rFBKaHXL8fDceHUi\n" + + "GOAG0dLqP+lxx/pPsgfW8dnu1h/I5+cvOsj/YmwLMlodhrGN0XpaWmATz7+ikif3\n" + + "VGGNXIWl/km+r30M4diFnSnycjYaOJdBqhLIkQd/w/JFFJ5J+C5b2281jYGw6Y1F\n" + + "Kq3pqLlQVCnQhcnDroCtwLK78hG7yZasYVBnjKilSkMB1k14Kfq8WUR3NsODRiXg\n" + + "EP+KsWrwS5l/cyUzkWDKgOvmlWeqSWp95WGhewuVAs34W0hzdT3JDd4TIX3NWMuw\n" + + "i9txCbagsrq/2+rKgsasCPlcQwFw6Umzd73HuqiHmoM=\n" + + "-----END CERTIFICATE-----\n"; } diff --git a/spring-security-jwt/src/test/java/org/springframework/security/jwt/crypto/sign/EllipticCurveVerifierTests.java b/spring-security-jwt/src/test/java/org/springframework/security/jwt/crypto/sign/EllipticCurveVerifierTests.java new file mode 100644 index 000000000..718c9016d --- /dev/null +++ b/spring-security-jwt/src/test/java/org/springframework/security/jwt/crypto/sign/EllipticCurveVerifierTests.java @@ -0,0 +1,156 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.jwt.crypto.sign; + +import org.junit.Test; +import org.springframework.security.jwt.codec.Codecs; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.Security; +import java.security.Signature; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; + +/** + * Tests for {@link EllipticCurveVerifier}. + * + * @author Joe Grandja + */ +public class EllipticCurveVerifierTests { + private final static String P256_CURVE = "P-256"; + private final static String P384_CURVE = "P-384"; + private final static String P521_CURVE = "P-521"; + private final static String SHA256_ECDSA_ALG = "SHA256withECDSA"; + private final static String SHA384_ECDSA_ALG = "SHA384withECDSA"; + private final static String SHA512_ECDSA_ALG = "SHA512withECDSA"; + + static { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + } + + @Test(expected = IllegalArgumentException.class) + public void constructorWhenUnsupportedCurveThenThrowIllegalArgumentException() { + new EllipticCurveVerifier(BigInteger.ONE, BigInteger.ONE, "unsupported-curve", SHA256_ECDSA_ALG); + } + + @Test + public void verifyWhenP256CurveAndSignatureMatchesThenVerificationPasses() throws Exception { + this.verifyWhenSignatureMatchesThenVerificationPasses(P256_CURVE, SHA256_ECDSA_ALG); + } + + @Test(expected = InvalidSignatureException.class) + public void verifyWhenP256CurveAndSignatureDoesNotMatchThenThrowInvalidSignatureException() throws Exception { + this.verifyWhenSignatureDoesNotMatchThenThrowInvalidSignatureException(P256_CURVE, SHA256_ECDSA_ALG); + } + + @Test + public void verifyWhenP384CurveAndSignatureMatchesThenVerificationPasses() throws Exception { + this.verifyWhenSignatureMatchesThenVerificationPasses(P384_CURVE, SHA384_ECDSA_ALG); + } + + @Test(expected = InvalidSignatureException.class) + public void verifyWhenP384CurveAndSignatureDoesNotMatchThenThrowInvalidSignatureException() throws Exception { + this.verifyWhenSignatureDoesNotMatchThenThrowInvalidSignatureException(P384_CURVE, SHA384_ECDSA_ALG); + } + + @Test + public void verifyWhenP521CurveAndSignatureMatchesThenVerificationPasses() throws Exception { + this.verifyWhenSignatureMatchesThenVerificationPasses(P521_CURVE, SHA512_ECDSA_ALG); + } + + @Test(expected = InvalidSignatureException.class) + public void verifyWhenP521CurveAndSignatureDoesNotMatchThenThrowInvalidSignatureException() throws Exception { + this.verifyWhenSignatureDoesNotMatchThenThrowInvalidSignatureException(P521_CURVE, SHA512_ECDSA_ALG); + } + + @Test(expected = InvalidSignatureException.class) + public void verifyWhenSignatureAlgorithmNotSameAsVerificationAlgorithmThenThrowInvalidSignatureException() throws Exception { + KeyPair keyPair = this.generateKeyPair(P256_CURVE); + ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic(); + ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate(); + + byte[] data = "Some data".getBytes(); + + byte[] jwsSignature = Codecs.b64UrlEncode(this.generateJwsSignature(data, SHA256_ECDSA_ALG, privateKey)); + + EllipticCurveVerifier verifier = new EllipticCurveVerifier( + publicKey.getW().getAffineX(), + publicKey.getW().getAffineY(), + P256_CURVE, + SHA512_ECDSA_ALG); + verifier.verify(data, Codecs.b64UrlDecode(jwsSignature)); + } + + private void verifyWhenSignatureMatchesThenVerificationPasses(String curve, String algorithm) throws Exception { + KeyPair keyPair = this.generateKeyPair(curve); + ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic(); + ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate(); + + byte[] data = "Some data".getBytes(); + + byte[] jwsSignature = Codecs.b64UrlEncode(this.generateJwsSignature(data, algorithm, privateKey)); + + EllipticCurveVerifier verifier = new EllipticCurveVerifier( + publicKey.getW().getAffineX(), + publicKey.getW().getAffineY(), + curve, + algorithm); + verifier.verify(data, Codecs.b64UrlDecode(jwsSignature)); + } + + private void verifyWhenSignatureDoesNotMatchThenThrowInvalidSignatureException(String curve, String algorithm) throws Exception { + KeyPair keyPair = this.generateKeyPair(curve); + ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic(); + ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate(); + + byte[] data = "Some data".getBytes(); + + byte[] jwsSignature = Codecs.b64UrlEncode(this.generateJwsSignature(data, algorithm, privateKey)); + + EllipticCurveVerifier verifier = new EllipticCurveVerifier( + publicKey.getW().getAffineX(), + publicKey.getW().getAffineY(), + curve, + algorithm); + verifier.verify("Data not matching signature".getBytes(), Codecs.b64UrlDecode(jwsSignature)); + } + + private KeyPair generateKeyPair(String curve) throws Exception { + ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(curve); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDSA"); + keyPairGenerator.initialize(ecGenParameterSpec); + return keyPairGenerator.generateKeyPair(); + } + + private byte[] generateJwsSignature(byte[] data, String algorithm, ECPrivateKey privateKey) throws Exception { + Signature signature = Signature.getInstance(algorithm); + signature.initSign(privateKey); + signature.update(data); + byte[] jcaSignature = signature.sign();// DER-encoded signature, according to JCA spec (sequence of two integers - R + S) + int jwsSignatureLength = -1; + if (SHA256_ECDSA_ALG.equals(algorithm)) { + jwsSignatureLength = 64; + } else if (SHA384_ECDSA_ALG.equals(algorithm)) { + jwsSignatureLength = 96; + } else if (SHA512_ECDSA_ALG.equals(algorithm)) { + jwsSignatureLength = 132; + } + return EllipticCurveSignatureHelper.transcodeSignatureToJWS(jcaSignature, jwsSignatureLength); + } +} diff --git a/spring-security-jwt/src/test/java/org/springframework/security/jwt/crypto/sign/RsaSigningTests.java b/spring-security-jwt/src/test/java/org/springframework/security/jwt/crypto/sign/RsaSigningTests.java index 7b41c46d4..da83c9bc3 100644 --- a/spring-security-jwt/src/test/java/org/springframework/security/jwt/crypto/sign/RsaSigningTests.java +++ b/spring-security-jwt/src/test/java/org/springframework/security/jwt/crypto/sign/RsaSigningTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -16,6 +16,8 @@ import org.springframework.security.jwt.codec.Codecs; import org.springframework.security.jwt.crypto.cipher.RsaTestKeyData; +import static org.junit.Assert.assertNotNull; + /** * @author Luke Taylor */ @@ -24,6 +26,19 @@ public class RsaSigningTests { @Test(expected = IllegalArgumentException.class) public void rsaSignerRejectsInvalidKey() throws Exception { RsaSigner signer = new RsaSigner(RsaTestKeyData.SSH_PUBLIC_KEY_STRING); + assertNotNull(signer); + } + + @Test + public void rsaSignerValidKeyWithWhitespace() throws Exception { + RsaSigner signer = new RsaSigner(RsaTestKeyData.SSH_PRIVATE_KEY_STRING_WITH_WHITESPACE); + assertNotNull(signer); + } + + @Test + public void rsaVerifierValidKeyWithoutComment() throws Exception { + RsaVerifier verifier = new RsaVerifier(RsaTestKeyData.SSH_PUBLIC_KEY_STRING_WITHOUT_COMMENT); + assertNotNull(verifier); } @Test @@ -32,12 +47,23 @@ public void keysFromPrivateAndPublicKeyStringDataAreCorrect() throws Exception { byte[] content = Codecs.utf8Encode("Hi I'm the data"); RsaSigner signer = new RsaSigner(RsaTestKeyData.SSH_PRIVATE_KEY_STRING); + final byte[] signed = signer.sign(content); // First extract the public key from the private key data RsaVerifier verifier = new RsaVerifier(RsaTestKeyData.SSH_PRIVATE_KEY_STRING); - verifier.verify(content, signer.sign(content)); + verifier.verify(content, signed); // Then try with the ssh-rsa public key format verifier = new RsaVerifier(RsaTestKeyData.SSH_PUBLIC_KEY_STRING); - verifier.verify(content, signer.sign(content)); + verifier.verify(content, signed); + + // Try with the PEM format public keys + verifier = new RsaVerifier(RsaTestKeyData.SSH_PUBLIC_KEY_PEM_STRING); + verifier.verify(content, signed); + + verifier = new RsaVerifier(RsaTestKeyData.SSH_PUBLIC_KEY_OPENSSL_PEM_STRING); + verifier.verify(content, signed); + + verifier = new RsaVerifier(RsaTestKeyData.SSH_X509_CERTIFICATE_PEM_STRING); + verifier.verify(content, signed); } } diff --git a/spring-security-oauth/.springBeans b/spring-security-oauth/.springBeans deleted file mode 100644 index 9989c639b..000000000 --- a/spring-security-oauth/.springBeans +++ /dev/null @@ -1,14 +0,0 @@ - - - 1 - - - - - - - src/test/resources/org/springframework/security/oauth/config/TestFilterChainInitialization-context.xml - - - - diff --git a/spring-security-oauth/pom.xml b/spring-security-oauth/pom.xml index 99e45724b..7ea72e61d 100644 --- a/spring-security-oauth/pom.xml +++ b/spring-security-oauth/pom.xml @@ -1,16 +1,29 @@ - + 4.0.0 org.springframework.security.oauth spring-security-oauth-parent - 1.0.1.BUILD-SNAPSHOT + 2.5.3.BUILD-SNAPSHOT spring-security-oauth OAuth 1(a) for Spring Security Module for providing OAuth support to Spring Security + + 3.0.1 + + + + + spring5 + + 3.1.0 + + + + @@ -44,112 +57,98 @@ - - com.springsource.bundlor - com.springsource.bundlor.maven - - - javax.servlet - servlet-api - 2.3 - provided - - org.springframework spring-beans - ${spring.version} org.springframework spring-core - ${spring.version} org.springframework spring-context - ${spring.version} org.springframework spring-web - ${spring.version} org.springframework spring-aop - ${spring.version} org.springframework spring-jdbc - ${spring.version} true org.springframework spring-test - ${spring.version} test org.springframework.security spring-security-core - ${spring.security.version} org.springframework.security spring-security-config - ${spring.security.version} org.springframework.security spring-security-web - ${spring.security.version} commons-codec commons-codec - 1.3 + + + + javax.servlet + javax.servlet-api + + ${servlet-api.version} + true junit junit - 4.8.2 + ${junit.version} test org.mockito mockito-core - 1.9.0 + ${mockito.version} test - net.oauth - oauth-core - 20090531 + net.oauth.core + oauth-provider + 20100527 test @@ -170,13 +169,13 @@ org.apache.httpcomponents httpclient - 4.1.1 + 4.5.13 org.slf4j slf4j-api - 1.5.8 + 1.7.6 test diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthCodec.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthCodec.java index 9390ab54a..393f6c3e2 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthCodec.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthCodec.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -25,8 +25,12 @@ /** * Utility for parameter encoding according to the OAuth spec. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class OAuthCodec extends URLCodec { protected static final BitSet SAFE_CHARACTERS = (BitSet) URLCodec.WWW_FORM_URL.clone(); @@ -67,6 +71,7 @@ public static String oauthEncode(String value) { * * @param value The value to decode. * @return The decoded value. + * @throws DecoderException when URLCodec fails */ public static String oauthDecode(String value) throws DecoderException { if (value == null) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthConsumerParameter.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthConsumerParameter.java index c485601d5..66e6bc1f5 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthConsumerParameter.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthConsumerParameter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,12 @@ /** * Enumeration for consumer parameters. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public enum OAuthConsumerParameter { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthException.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthException.java index 7658b1f29..9c8b44a6a 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthException.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -20,9 +20,14 @@ /** * Base exception for OAuth processing. - * + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class OAuthException extends AuthenticationException { public OAuthException(String message) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthProviderParameter.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthProviderParameter.java index 30ce020ab..af5c8be4d 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthProviderParameter.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/OAuthProviderParameter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,12 @@ /** * Parameters that can be used by the provider. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public enum OAuthProviderParameter { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/StringSplitUtils.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/StringSplitUtils.java index 125389211..e2a2045c9 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/StringSplitUtils.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/StringSplitUtils.java @@ -10,14 +10,19 @@ /** * Provides several String manipulation methods. Copied from deleted org.springframework.security.util.StringSplitUtils + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * */ +@Deprecated public class StringSplitUtils { private static final String[] EMPTY_STRING_ARRAY = new String[0]; /** - * Splits a String at the first instance of the delimiter.

Does not include the delimiter in - * the response.

+ * Splits a String at the first instance of the delimiter. Does not include the delimiter in + * the response. * * @param toSplit the string to split * @param delimiter to split the string up with @@ -49,7 +54,7 @@ public static String[] split(String toSplit, String delimiter) { * Takes an array of Strings, and for each element removes any instances of * removeCharacter, and splits the element based on the delimiter. A Map is * then generated, with the left of the delimiter providing the key, and the right of the delimiter providing the - * value.

Will trim both the key and value before adding to the Map.

+ * value. Will trim both the key and value before adding to the Map. * * @param array the array to process * @param delimiter to split each element using (typically the equals symbol) @@ -90,8 +95,11 @@ public static Map splitEachArrayElementAndCreateMap(String[] arr * Splits a given string on the given separator character, skips the contents of quoted substrings * when looking for separators. * Introduced for use in DigestProcessingFilter (see SEC-506). - *

+ * * This was copied and modified from commons-lang StringUtils + * @param str the string to split + * @param separatorChar the character by which to split the string + * @return String array containing split string */ public static String[] splitIgnoringQuotes(String str, char separatorChar) { if (str == null) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/CoreOAuthSignatureMethodFactory.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/CoreOAuthSignatureMethodFactory.java index 07f4e0758..b8abb0940 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/CoreOAuthSignatureMethodFactory.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/CoreOAuthSignatureMethodFactory.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -30,8 +30,12 @@ /** * Implements the signatures defined in OAuth Core 1.0. By default, PLAINTEXT signatures are not supported * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class CoreOAuthSignatureMethodFactory implements OAuthSignatureMethodFactory { private boolean supportPlainText = false; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/HMAC_SHA1SignatureMethod.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/HMAC_SHA1SignatureMethod.java index 88e78edab..baf4c0b52 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/HMAC_SHA1SignatureMethod.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/HMAC_SHA1SignatureMethod.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -29,8 +29,12 @@ /** * HMAC-SHA1 signature method. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class HMAC_SHA1SignatureMethod implements OAuthSignatureMethod { private static final Log LOG = LogFactory.getLog(HMAC_SHA1SignatureMethod.class); diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/InvalidSignatureException.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/InvalidSignatureException.java index fba700661..0ff6d2cb5 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/InvalidSignatureException.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/InvalidSignatureException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,8 +21,13 @@ /** * Thrown when a signature is invalid. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class InvalidSignatureException extends OAuthException { public InvalidSignatureException(String msg) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/OAuthSignatureMethod.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/OAuthSignatureMethod.java index 01c85c3e2..883439681 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/OAuthSignatureMethod.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/OAuthSignatureMethod.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -17,8 +17,12 @@ package org.springframework.security.oauth.common.signature; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthSignatureMethod { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/OAuthSignatureMethodFactory.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/OAuthSignatureMethodFactory.java index a26a88a80..7aed490ca 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/OAuthSignatureMethodFactory.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/OAuthSignatureMethodFactory.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,12 @@ /** * Factory for signature methods. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthSignatureMethodFactory { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/PlainTextSignatureMethod.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/PlainTextSignatureMethod.java index 4b9be8946..673b24f60 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/PlainTextSignatureMethod.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/PlainTextSignatureMethod.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,8 +21,12 @@ /** * Plain text signature method. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class PlainTextSignatureMethod implements OAuthSignatureMethod { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/RSAKeySecret.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/RSAKeySecret.java index cad84ec10..e4c1d8a68 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/RSAKeySecret.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/RSAKeySecret.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -32,8 +32,13 @@ /** * Signature secret for RSA. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class RSAKeySecret implements SignatureSecret { private final PrivateKey privateKey; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/RSA_SHA1SignatureMethod.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/RSA_SHA1SignatureMethod.java index 8de859e3f..f027f84df 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/RSA_SHA1SignatureMethod.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/RSA_SHA1SignatureMethod.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -25,8 +25,12 @@ * RSA-SHA1 signature method. The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in RFC3447 * section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for EMSA-PKCS1-v1_5. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class RSA_SHA1SignatureMethod implements OAuthSignatureMethod { /** @@ -79,9 +83,9 @@ public String getName() { /** * The Signature Base String is signed using the Consumer’s RSA private key per RFC3447 section 8.2.1, where K is the Consumer’s RSA private key, - * M the Signature Base String, and S is the result signature octet string:

+ * M the Signature Base String, and S is the result signature octet string: * - * S = RSASSA-PKCS1-V1_5-SIGN (K, M)

+ * {@code S = RSASSA-PKCS1-V1_5-SIGN (K, M) } * * oauth_signature is set to S, first base64-encoded per RFC2045 section 6.8, then URL-encoded per Parameter Encoding. * @@ -172,4 +176,4 @@ public PrivateKey getPrivateKey() { public PublicKey getPublicKey() { return publicKey; } -} \ No newline at end of file +} diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SaltedConsumerSecret.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SaltedConsumerSecret.java index 7b5250506..ad433b5f5 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SaltedConsumerSecret.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SaltedConsumerSecret.java @@ -3,8 +3,12 @@ /** * Marker interface for indicating that a consumer secret has some salt. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface SaltedConsumerSecret { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SharedConsumerSecret.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SharedConsumerSecret.java index c2ac4f6c1..ef627ccf3 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SharedConsumerSecret.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SharedConsumerSecret.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,9 +19,13 @@ /** * A signature secret that consists of a consumer secret and a token secret. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton - * @author + * @author Aliaksandr Autayeu */ +@Deprecated public interface SharedConsumerSecret extends SignatureSecret { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SharedConsumerSecretImpl.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SharedConsumerSecretImpl.java index cd84d7d30..6eb66901f 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SharedConsumerSecretImpl.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SharedConsumerSecretImpl.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,13 @@ /** * Default implementation of a signature secret. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class SharedConsumerSecretImpl implements SharedConsumerSecret { private final String consumerSecret; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SignatureSecret.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SignatureSecret.java index b695d5e9c..44cde648b 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SignatureSecret.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SignatureSecret.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -20,9 +20,13 @@ /** * Marker interface for OAuth signature secrets. - * + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public interface SignatureSecret extends Serializable { } diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SignatureSecretEditor.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SignatureSecretEditor.java index 64695177b..874e3624b 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SignatureSecretEditor.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/SignatureSecretEditor.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -20,9 +20,13 @@ /** * A signature secret that consists of a consumer secret and a tokent secret. - * + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class SignatureSecretEditor extends PropertyEditorSupport { public void setAsText(String text) throws IllegalArgumentException { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/UnsupportedSignatureMethodException.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/UnsupportedSignatureMethodException.java index e1d233ba2..be7852485 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/UnsupportedSignatureMethodException.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/common/signature/UnsupportedSignatureMethodException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -17,8 +17,13 @@ package org.springframework.security.oauth.common.signature; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class UnsupportedSignatureMethodException extends RuntimeException { public UnsupportedSignatureMethodException(String msg) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ConfigUtils.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ConfigUtils.java index 2f2b8e434..2922fad7e 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ConfigUtils.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ConfigUtils.java @@ -1,5 +1,6 @@ package org.springframework.security.oauth.config; +import java.lang.reflect.Method; import java.util.Collections; import java.util.List; @@ -13,16 +14,27 @@ import org.springframework.security.config.BeanIds; import org.springframework.security.config.http.MatcherType; import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; + /** * Common place for OAuth namespace configuration utils. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class ConfigUtils { + private static final Method createMatcherMethod3x = ReflectionUtils.findMethod( + MatcherType.class, "createMatcher", String.class, String.class); + private static final Method createMatcherMethod4x = ReflectionUtils.findMethod( + MatcherType.class, "createMatcher", ParserContext.class, String.class, String.class); + private ConfigUtils() { } @@ -56,7 +68,13 @@ public static BeanDefinition createSecurityMetadataSource(Element element, Parse String access = filterPattern.getAttribute("resources"); if (StringUtils.hasText(access)) { - BeanDefinition matcher = matcherType.createMatcher(path, method); + BeanDefinition matcher; + if (createMatcherMethod4x != null) { + matcher = (BeanDefinition)ReflectionUtils.invokeMethod(createMatcherMethod4x, matcherType, pc, path, method); + } else { + matcher = (BeanDefinition)ReflectionUtils.invokeMethod(createMatcherMethod3x, matcherType, path, method); + } + if (access.equals("none")) { invocationDefinitionMap.put(matcher, BeanDefinitionBuilder.rootBeanDefinition(Collections.class).setFactoryMethod("emptyList").getBeanDefinition()); } diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ConsumerDetailsFactoryBean.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ConsumerDetailsFactoryBean.java index 433904ccc..ad3c1e247 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ConsumerDetailsFactoryBean.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ConsumerDetailsFactoryBean.java @@ -1,10 +1,10 @@ /* - * Copyright 2006-2011 the original author or authors. + * Copyright 2006-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 * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -13,10 +13,13 @@ package org.springframework.security.oauth.config; import java.io.IOException; +import java.io.InputStream; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.FactoryBean; import org.springframework.context.ResourceLoaderAware; @@ -29,11 +32,16 @@ import org.springframework.security.oauth.provider.ConsumerDetails; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Dave Syer * */ +@Deprecated public class ConsumerDetailsFactoryBean implements FactoryBean, ResourceLoaderAware { - + + private static final Log logger = LogFactory.getLog(ConsumerDetailsFactoryBean.class); private Object typeOfSecret; private BaseConsumerDetails consumer = new BaseConsumerDetails(); private String secret; @@ -81,19 +89,31 @@ public void setTypeOfSecret(Object typeOfSecret) { public ConsumerDetails getObject() throws Exception { if ("rsa-cert".equals(typeOfSecret)) { + InputStream inputStream = null; try { - Certificate cert = CertificateFactory.getInstance("X.509").generateCertificate(resourceLoader.getResource(secret).getInputStream()); + inputStream = resourceLoader.getResource(secret).getInputStream(); + Certificate cert = CertificateFactory.getInstance("X.509").generateCertificate(inputStream); consumer.setSignatureSecret(new RSAKeySecret(cert.getPublicKey())); } catch (IOException e) { - throw new BeanCreationException("RSA certificate not found at " + secret + ".", + throw new BeanCreationException("RSA certificate not found", e); } catch (CertificateException e) { - throw new BeanCreationException("Invalid RSA certificate at " + secret + ".", e); + throw new BeanCreationException("Invalid RSA certificate", e); } catch (NullPointerException e) { - throw new BeanCreationException("Could not load RSA certificate at " + secret + ".", e); + throw new BeanCreationException("Could not load RSA certificate", e); + } + finally { + try { + if (inputStream != null) { + inputStream.close(); + } + } + catch (IOException e) { + logger.warn("Cannot close open stream: ", e); + } } } else { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ConsumerServiceBeanDefinitionParser.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ConsumerServiceBeanDefinitionParser.java index de91a6097..3f03de13c 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ConsumerServiceBeanDefinitionParser.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ConsumerServiceBeanDefinitionParser.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -29,10 +29,14 @@ import org.w3c.dom.Element; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Andrew McCall * @author Dave Syer */ +@Deprecated public class ConsumerServiceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ExpressionHandlerBeanDefinitionParser.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ExpressionHandlerBeanDefinitionParser.java index 333fa1607..0bc457f54 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ExpressionHandlerBeanDefinitionParser.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ExpressionHandlerBeanDefinitionParser.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,9 +21,13 @@ import org.w3c.dom.Element; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class ExpressionHandlerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/OAuthConsumerBeanDefinitionParser.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/OAuthConsumerBeanDefinitionParser.java index 025c3eeb0..c984e495a 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/OAuthConsumerBeanDefinitionParser.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/OAuthConsumerBeanDefinitionParser.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -34,10 +34,14 @@ /** * Parser for the OAuth "consumer" element. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Andrew McCall * @author Luke Taylor */ +@Deprecated public class OAuthConsumerBeanDefinitionParser implements BeanDefinitionParser { public BeanDefinition parse(Element element, ParserContext parserContext) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/OAuthProviderBeanDefinitionParser.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/OAuthProviderBeanDefinitionParser.java index dcb201c3e..6b1721050 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/OAuthProviderBeanDefinitionParser.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/OAuthProviderBeanDefinitionParser.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -39,9 +39,13 @@ /** * Parser for the OAuth "provider" element. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Andrew McCall */ +@Deprecated public class OAuthProviderBeanDefinitionParser implements BeanDefinitionParser { public BeanDefinition parse(Element element, ParserContext parserContext) { @@ -203,7 +207,7 @@ private int insertIndex(List filterChain) { BeanMetadataElement filter = filterChain.get(i); if (filter instanceof BeanDefinition) { String beanName = ((BeanDefinition) filter).getBeanClassName(); - if (beanName.equals(ExceptionTranslationFilter.class.getName())) { + if (ExceptionTranslationFilter.class.getName().equals(beanName)) { return i + 1; } } diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/OAuthSecurityNamespaceHandler.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/OAuthSecurityNamespaceHandler.java index e92a268ea..f45565316 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/OAuthSecurityNamespaceHandler.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/OAuthSecurityNamespaceHandler.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,12 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class OAuthSecurityNamespaceHandler extends NamespaceHandlerSupport { public void init() { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ProtectedResourceDetailsBeanDefinitionParser.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ProtectedResourceDetailsBeanDefinitionParser.java index c12d19820..4e77bbc56 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ProtectedResourceDetailsBeanDefinitionParser.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ProtectedResourceDetailsBeanDefinitionParser.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -31,8 +31,12 @@ import java.util.Map; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class ProtectedResourceDetailsBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ProtectedResourceDetailsServiceFactoryBean.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ProtectedResourceDetailsServiceFactoryBean.java index 81fe4856f..e10b20015 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ProtectedResourceDetailsServiceFactoryBean.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/ProtectedResourceDetailsServiceFactoryBean.java @@ -12,8 +12,12 @@ /** * Factory bean for the resource details service. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class ProtectedResourceDetailsServiceFactoryBean extends AbstractFactoryBean { @Override diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/TokenServiceBeanDefinitionParser.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/TokenServiceBeanDefinitionParser.java index 84c9095f9..a63e08ade 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/TokenServiceBeanDefinitionParser.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/TokenServiceBeanDefinitionParser.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -24,8 +24,12 @@ import org.w3c.dom.Element; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class TokenServiceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/VerifierServiceBeanDefinitionParser.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/VerifierServiceBeanDefinitionParser.java index b22bec73d..d280e5c3d 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/VerifierServiceBeanDefinitionParser.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/config/VerifierServiceBeanDefinitionParser.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -24,8 +24,12 @@ import org.w3c.dom.Element; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class VerifierServiceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/AccessTokenRequiredException.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/AccessTokenRequiredException.java index 8ef810f14..715973b2d 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/AccessTokenRequiredException.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/AccessTokenRequiredException.java @@ -3,8 +3,13 @@ import org.springframework.security.authentication.InsufficientAuthenticationException; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class AccessTokenRequiredException extends InsufficientAuthenticationException { private final ProtectedResourceDetails resource; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/BaseProtectedResourceDetails.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/BaseProtectedResourceDetails.java index 8687c8de3..53059dda2 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/BaseProtectedResourceDetails.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/BaseProtectedResourceDetails.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -24,8 +24,12 @@ /** * Basic implementation of protected resource details. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class BaseProtectedResourceDetails implements ProtectedResourceDetails { private String id; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/InMemoryProtectedResourceDetailsService.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/InMemoryProtectedResourceDetailsService.java index 7b0ca51fe..a0c188114 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/InMemoryProtectedResourceDetailsService.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/InMemoryProtectedResourceDetailsService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -22,8 +22,12 @@ /** * Basic, in-memory implementation of a protected resource details service. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class InMemoryProtectedResourceDetailsService implements ProtectedResourceDetailsService { private Map resourceDetailsStore = new HashMap(); diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/InvalidOAuthRealmException.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/InvalidOAuthRealmException.java index d80e4c700..8da4d544d 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/InvalidOAuthRealmException.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/InvalidOAuthRealmException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -18,9 +18,14 @@ /** * Thrown when a different realm appears to be the cause of the authorization failure. - * + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class InvalidOAuthRealmException extends OAuthRequestFailedException { private final String requiredRealm; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthConsumerSupport.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthConsumerSupport.java index 701a1b9e7..4ae98786f 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthConsumerSupport.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthConsumerSupport.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -24,8 +24,12 @@ /** * Consumer-side support for OAuth. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthConsumerSupport { /** @@ -45,7 +49,7 @@ public interface OAuthConsumerSupport { * @return The unauthorized request token. */ OAuthConsumerToken getUnauthorizedRequestToken(ProtectedResourceDetails resource, String callback) throws OAuthRequestFailedException; - + /** * Get an access token for a protected resource. * @@ -64,7 +68,7 @@ public interface OAuthConsumerSupport { * @return The access token. */ OAuthConsumerToken getAccessToken(ProtectedResourceDetails resource, OAuthConsumerToken requestToken, String verifier); - + /** * Read a protected resource from the given URL using the specified access token and HTTP method. * @@ -104,9 +108,9 @@ public interface OAuthConsumerSupport { * Get the query string that is to be used in the given request. The query string will * include any custom query parameters in the URL and any necessary OAuth parameters. Note, * however, that an OAuth parameter is not considered "necessary" if the provider of the resource - * supports the authorization header.

+ * supports the authorization header. * - * Any OAuth parameters will be URL-encoded, but not oauth-encoded, per the OAuth spec.

+ * Any OAuth parameters will be URL-encoded, but not oauth-encoded, per the OAuth spec. * * The query string is to be used by either applying it to the URL (for HTTP GET) or putting it * in the body of the request (for HTTP POST). diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthConsumerToken.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthConsumerToken.java index 4ab2ecaaf..1e93577b5 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthConsumerToken.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthConsumerToken.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,9 +21,13 @@ /** * Interface for a consumer-side OAuth token. - * + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class OAuthConsumerToken implements Serializable { private static final long serialVersionUID = -4057986970456346647L; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthRequestFailedException.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthRequestFailedException.java index 7314a556c..60c735d05 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthRequestFailedException.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthRequestFailedException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,8 +21,13 @@ /** * Thrown when an OAuth request fails. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class OAuthRequestFailedException extends AccessDeniedException { public OAuthRequestFailedException(String msg) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthSecurityContext.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthSecurityContext.java index 3a5ce0789..7e2a5e34e 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthSecurityContext.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthSecurityContext.java @@ -6,8 +6,12 @@ /** * The OAuth 2 security context (for a specific user). * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthSecurityContext { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthSecurityContextHolder.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthSecurityContextHolder.java index 6ed43ad66..f61b05497 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthSecurityContextHolder.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthSecurityContextHolder.java @@ -3,8 +3,12 @@ /** * Holder for the current OAuth security context. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class OAuthSecurityContextHolder { private static final ThreadLocal CURRENT_CONTEXT = new ThreadLocal(); diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthSecurityContextImpl.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthSecurityContextImpl.java index 176240897..9a05d76b3 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthSecurityContextImpl.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/OAuthSecurityContextImpl.java @@ -4,8 +4,12 @@ import java.util.Map; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class OAuthSecurityContextImpl implements OAuthSecurityContext { private Map accessTokens; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/ProtectedResourceDetails.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/ProtectedResourceDetails.java index f52181a6a..901137d4a 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/ProtectedResourceDetails.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/ProtectedResourceDetails.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -23,9 +23,13 @@ /** * Details about a protected resource. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Andrew McCall */ +@Deprecated public interface ProtectedResourceDetails { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/ProtectedResourceDetailsService.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/ProtectedResourceDetailsService.java index edd28ae21..a6c6037aa 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/ProtectedResourceDetailsService.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/ProtectedResourceDetailsService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,12 @@ /** * Service for loading protected resource details. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface ProtectedResourceDetailsService { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/UnverifiedRequestTokenException.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/UnverifiedRequestTokenException.java index 383dced02..68cd47791 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/UnverifiedRequestTokenException.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/UnverifiedRequestTokenException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,13 @@ /** * Thrown when an attempt is made to use an unverified request token. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class UnverifiedRequestTokenException extends OAuthRequestFailedException { public UnverifiedRequestTokenException(String msg) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/client/CoreOAuthConsumerSupport.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/client/CoreOAuthConsumerSupport.java index 3852a8203..cfec33cc0 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/client/CoreOAuthConsumerSupport.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/client/CoreOAuthConsumerSupport.java @@ -1,11 +1,11 @@ /* - * Copyright 2008-2009 Web Cohesion + * Copyright 2008-2019 Web Cohesion * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -17,6 +17,8 @@ package org.springframework.security.oauth.consumer.client; import org.apache.commons.codec.DecoderException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth.common.OAuthCodec; @@ -50,11 +52,16 @@ * OAuth provider. A proxy will be selected, but it is assumed that the {@link javax.net.ssl.TrustManager}s * and other connection-related environment variables are already set up. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Andrew McCall */ +@Deprecated public class CoreOAuthConsumerSupport implements OAuthConsumerSupport, InitializingBean { + private static final Log logger = LogFactory.getLog(CoreOAuthConsumerSupport.class); private OAuthURLStreamHandlerFactory streamHandlerFactory; private OAuthSignatureMethodFactory signatureFactory = new CoreOAuthSignatureMethodFactory(); private NonceFactory nonceFactory = new UUIDNonceFactory(); @@ -131,7 +138,7 @@ public OAuthConsumerToken getAccessToken(ProtectedResourceDetails details, OAuth Map additionalParameters = new TreeMap(); if (details.isUse10a()) { if (verifier == null) { - throw new UnverifiedRequestTokenException("Unverified request token: " + requestToken); + throw new UnverifiedRequestTokenException("Unverified request token"); } additionalParameters.put(OAuthConsumerParameter.oauth_verifier.toString(), verifier); } @@ -196,15 +203,15 @@ protected InputStream readResource(ProtectedResourceDetails details, URL url, St int responseCode; String responseMessage; + OutputStream out = null; try { connection.setDoOutput(sendOAuthParamsInRequestBody); connection.connect(); if (sendOAuthParamsInRequestBody) { String queryString = getOAuthQueryString(details, token, url, httpMethod, additionalParameters); - OutputStream out = connection.getOutputStream(); + out = connection.getOutputStream(); out.write(queryString.getBytes("UTF-8")); out.flush(); - out.close(); } responseCode = connection.getResponseCode(); responseMessage = connection.getResponseMessage(); @@ -215,6 +222,16 @@ protected InputStream readResource(ProtectedResourceDetails details, URL url, St catch (IOException e) { throw new OAuthRequestFailedException("OAuth connection failed.", e); } + finally { + try { + if (out != null) { + out.close(); + } + } + catch (IOException e) { + logger.warn("Cannot close open stream: ", e); + } + } if (responseCode >= 200 && responseCode < 300) { try { @@ -431,6 +448,16 @@ protected OAuthConsumerToken getTokenFromProvider(ProtectedResourceDetails detai catch (IOException e) { throw new OAuthRequestFailedException("Unable to read the token.", e); } + finally { + try { + if (inputStream != null) { + inputStream.close(); + } + } + catch (IOException e) { + logger.warn("Cannot close open stream: ", e); + } + } StringTokenizer tokenProperties = new StringTokenizer(tokenInfo, "&"); Map tokenPropertyValues = new TreeMap(); @@ -647,16 +674,17 @@ protected String getSignatureBaseString(Map> oauthPara Iterator>> sortedIt = sortedParameters.entrySet().iterator(); while (sortedIt.hasNext()) { Map.Entry> sortedParameter = sortedIt.next(); - for (String parameterValue : sortedParameter.getValue()) { - if (parameterValue == null) { + for (Iterator sortedParametersIterator = sortedParameter.getValue().iterator(); sortedParametersIterator.hasNext();) { + String parameterValue = sortedParametersIterator.next(); + if (parameterValue == null) { parameterValue = ""; } queryString.append(sortedParameter.getKey()).append('=').append(parameterValue); - if (sortedIt.hasNext()) { + if (sortedIt.hasNext() || sortedParametersIterator.hasNext()) { queryString.append('&'); } - } + } } StringBuilder url = new StringBuilder(requestURL.getProtocol().toLowerCase()).append("://").append(requestURL.getHost().toLowerCase()); diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/client/OAuthClientHttpRequestFactory.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/client/OAuthClientHttpRequestFactory.java index 1ee398580..d7015365c 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/client/OAuthClientHttpRequestFactory.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/client/OAuthClientHttpRequestFactory.java @@ -1,9 +1,5 @@ package org.springframework.security.oauth.consumer.client; -import java.io.IOException; -import java.net.URI; -import java.util.Map; - import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; @@ -13,12 +9,23 @@ import org.springframework.security.oauth.consumer.OAuthSecurityContextHolder; import org.springframework.security.oauth.consumer.OAuthSecurityContextImpl; import org.springframework.security.oauth.consumer.ProtectedResourceDetails; +import org.springframework.util.CollectionUtils; + +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; /** * Request factory that extends all http requests with the OAuth credentials for a specific protected resource. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class OAuthClientHttpRequestFactory implements ClientHttpRequestFactory { private final ClientHttpRequestFactory delegate; @@ -37,6 +44,9 @@ public OAuthClientHttpRequestFactory(ClientHttpRequestFactory delegate, Protecte if (resource == null) { throw new IllegalArgumentException("A resource must be supplied for an OAuth2ClientHttpRequestFactory."); } + this.additionalOAuthParameters = !CollectionUtils.isEmpty(resource.getAdditionalParameters()) ? + new HashMap(resource.getAdditionalParameters()) : + Collections.emptyMap(); } public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { @@ -52,7 +62,7 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO if (!useAuthHeader) { String queryString = this.support.getOAuthQueryString(this.resource, accessToken, uri.toURL(), httpMethod.name(), this.additionalOAuthParameters); String uriValue = String.valueOf(uri); - uri = URI.create(uriValue.contains("?") ? uriValue + "&" + queryString : uriValue + "?" + queryString); + uri = URI.create((uriValue.contains("?") ? uriValue.substring(0, uriValue.indexOf('?')) : uriValue) + "?" + queryString); } ClientHttpRequest req = delegate.createRequest(uri, httpMethod); diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/client/OAuthRestTemplate.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/client/OAuthRestTemplate.java index 0d2e6b1fe..59d323394 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/client/OAuthRestTemplate.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/client/OAuthRestTemplate.java @@ -9,8 +9,12 @@ /** * Rest template that is able to make OAuth-authenticated REST requests with the credentials of the provided resource. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class OAuthRestTemplate extends RestTemplate { private final ProtectedResourceDetails resource; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/filter/OAuthConsumerContextFilter.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/filter/OAuthConsumerContextFilter.java index f79895ccc..5d8612fa0 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/filter/OAuthConsumerContextFilter.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/filter/OAuthConsumerContextFilter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,10 +16,26 @@ package org.springframework.security.oauth.consumer.filter; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; @@ -46,22 +62,15 @@ import org.springframework.security.web.util.ThrowableCauseExtractor; import org.springframework.util.Assert; -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeMap; - /** * OAuth filter that establishes an OAuth security context. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class OAuthConsumerContextFilter implements Filter, InitializingBean, MessageSourceAware { public static final String ACCESS_TOKENS_DEFAULT_ATTRIBUTE = "OAUTH_ACCESS_TOKENS"; @@ -245,6 +254,8 @@ else if (ex instanceof RuntimeException) { * * @param ex The exception. * @return The resource that needed authorization (never null). + * @throws ServletException in the case of an underlying Servlet API exception + * @throws IOException in the case of general IO exceptions */ protected ProtectedResourceDetails checkForResourceThatNeedsAuthorization(Exception ex) throws ServletException, IOException { Throwable[] causeChain = getThrowableAnalyzer().determineCauseChain(ex); @@ -317,6 +328,8 @@ protected String getUserAuthorizationRedirectURL(ProtectedResourceDetails detail * @param request The request. * @param response The response. * @param failure The failure. + * @throws ServletException in the case of an underlying Servlet API exception + * @throws IOException in the case of general IO exceptions */ protected void fail(HttpServletRequest request, HttpServletResponse response, OAuthRequestFailedException failure) throws IOException, ServletException { try { @@ -398,7 +411,6 @@ public OAuthConsumerSupport getConsumerSupport() { * * @param consumerSupport The OAuth consumer support. */ - @Autowired public void setConsumerSupport(OAuthConsumerSupport consumerSupport) { this.consumerSupport = consumerSupport; } @@ -435,7 +447,6 @@ public PortResolver getPortResolver() { * * @param portResolver The port resolver. */ - @Autowired ( required = false ) public void setPortResolver(PortResolver portResolver) { this.portResolver = portResolver; } @@ -513,4 +524,4 @@ public Throwable extractCause(Throwable throwable) { }); } } -} \ No newline at end of file +} diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/filter/OAuthConsumerProcessingFilter.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/filter/OAuthConsumerProcessingFilter.java index a23e9731e..29674ce68 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/filter/OAuthConsumerProcessingFilter.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/filter/OAuthConsumerProcessingFilter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -48,14 +48,18 @@ import java.util.TreeSet; /** - * OAuth consumer processing filter. This filter should be applied to requests for OAuth protected resources (see OAuth Core 1.0).

- *

+ * OAuth consumer processing filter. This filter should be applied to requests for OAuth protected resources (see OAuth Core 1.0). + * * When servicing a request that requires protected resources, this filter sets a request attribute (default "OAUTH_ACCESS_TOKENS") that contains * the list of {@link org.springframework.security.oauth.consumer.OAuthConsumerToken}s. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Andrew McCall */ +@Deprecated public class OAuthConsumerProcessingFilter implements Filter, InitializingBean, MessageSourceAware { private static final Log LOG = LogFactory.getLog(OAuthConsumerProcessingFilter.class); @@ -200,4 +204,4 @@ public void setRequireAuthenticated(boolean requireAuthenticated) { this.requireAuthenticated = requireAuthenticated; } -} \ No newline at end of file +} diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/DefaultOAuthURLStreamHandlerFactory.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/DefaultOAuthURLStreamHandlerFactory.java index eb415be7b..db424e879 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/DefaultOAuthURLStreamHandlerFactory.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/DefaultOAuthURLStreamHandlerFactory.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -26,8 +26,12 @@ /** * Default implementation. Assumes we're running on Sun's JVM. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class DefaultOAuthURLStreamHandlerFactory implements OAuthURLStreamHandlerFactory { public URLStreamHandler getHttpStreamHandler(ProtectedResourceDetails resourceDetails, OAuthConsumerToken accessToken, OAuthConsumerSupport support, String httpMethod, Map additionalParameters) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/OAuthOverHttpURLStreamHandler.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/OAuthOverHttpURLStreamHandler.java index 5ce53d96b..055981f69 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/OAuthOverHttpURLStreamHandler.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/OAuthOverHttpURLStreamHandler.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -30,9 +30,13 @@ /** * Stream handler to handle the request stream to a protected resource over HTTP. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ @SuppressWarnings("restriction") +@Deprecated public class OAuthOverHttpURLStreamHandler extends sun.net.www.protocol.http.Handler { private final ProtectedResourceDetails resourceDetails; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/OAuthOverHttpsURLStreamHandler.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/OAuthOverHttpsURLStreamHandler.java index de6bbbbd1..7646b0f5b 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/OAuthOverHttpsURLStreamHandler.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/OAuthOverHttpsURLStreamHandler.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -30,9 +30,13 @@ /** * Stream handler to handle the request stream to a protected resource over HTTP. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ @SuppressWarnings("restriction") +@Deprecated public class OAuthOverHttpsURLStreamHandler extends sun.net.www.protocol.https.Handler { private final ProtectedResourceDetails resourceDetails; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/OAuthURLStreamHandlerFactory.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/OAuthURLStreamHandlerFactory.java index 507088b1a..b34172184 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/OAuthURLStreamHandlerFactory.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/net/OAuthURLStreamHandlerFactory.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -26,8 +26,12 @@ /** * Factory for a OAuth URL stream handlers. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthURLStreamHandlerFactory { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/nonce/NonceFactory.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/nonce/NonceFactory.java index 82ff87a52..208190451 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/nonce/NonceFactory.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/nonce/NonceFactory.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,12 @@ /** * A nonce factory. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface NonceFactory { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/nonce/UUIDNonceFactory.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/nonce/UUIDNonceFactory.java index 028a47319..af5906ad3 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/nonce/UUIDNonceFactory.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/nonce/UUIDNonceFactory.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,8 +21,12 @@ /** * Nonce factory that uses a UUID to generate the nonce. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class UUIDNonceFactory implements NonceFactory { public String generateNonce() { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/rememberme/HttpSessionOAuthRememberMeServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/rememberme/HttpSessionOAuthRememberMeServices.java index ddf698688..b7b8a5fc1 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/rememberme/HttpSessionOAuthRememberMeServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/rememberme/HttpSessionOAuthRememberMeServices.java @@ -1,34 +1,61 @@ package org.springframework.security.oauth.consumer.rememberme; -import org.springframework.security.oauth.consumer.OAuthConsumerToken; +import java.util.HashMap; +import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import java.util.Map; + +import org.springframework.security.oauth.consumer.OAuthConsumerToken; /** - * Default implementation of the OAuth2 rememberme services. Just stores everything in the session. - * + * Default implementation of the OAuth2 rememberme services. Just stores everything in the session by default. Storing + * access token can be suppressed to reduce long-term expose of these tokens in the underlying HTTP session. + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton + * @author Alex Rau */ +@Deprecated public class HttpSessionOAuthRememberMeServices implements OAuthRememberMeServices { - public static final String REMEMBERED_TOKENS_KEY = HttpSessionOAuthRememberMeServices.class.getName() + "#REMEMBERED_TOKENS"; - - public Map loadRememberedTokens(HttpServletRequest request, HttpServletResponse response) { - HttpSession session = request.getSession(false); - Map rememberedTokens = null; - if (session != null) { - rememberedTokens = (Map) session.getAttribute(REMEMBERED_TOKENS_KEY); - } - return rememberedTokens; - } - - public void rememberTokens(Map tokens, HttpServletRequest request, HttpServletResponse response) { - HttpSession session = request.getSession(false); - if (session != null) { - session.setAttribute(REMEMBERED_TOKENS_KEY, tokens); - } - } + public static final String REMEMBERED_TOKENS_KEY = HttpSessionOAuthRememberMeServices.class.getName() + + "#REMEMBERED_TOKENS"; + + private boolean storeAccessTokens = true; + + @SuppressWarnings("unchecked") + public Map loadRememberedTokens(HttpServletRequest request, HttpServletResponse response) { + + HttpSession session = request.getSession(false); + + if (session != null) { + return (Map) session.getAttribute(REMEMBERED_TOKENS_KEY); + } + + return null; + } + + public void rememberTokens(Map tokens, HttpServletRequest request, + HttpServletResponse response) { + + HttpSession session = request.getSession(false); + + if (session == null) { + return; + } + + Map requestTokensOnly = new HashMap(); + + for (Map.Entry token : tokens.entrySet()) { + if (storeAccessTokens && !token.getValue().isAccessToken()) + requestTokensOnly.put(token.getKey(), token.getValue()); + + } + + session.setAttribute(REMEMBERED_TOKENS_KEY, requestTokensOnly); + } } diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/rememberme/NoOpOAuthRememberMeServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/rememberme/NoOpOAuthRememberMeServices.java index 0d08b2513..4eb6e8e9a 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/rememberme/NoOpOAuthRememberMeServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/rememberme/NoOpOAuthRememberMeServices.java @@ -10,9 +10,13 @@ * Basic, no-op implementation of the remember-me services. Not very useful in a 3-legged OAuth flow, but for a 2-legged * system where there are no request tokens to store in between requests it keeps the consumer stateless at the price of * obtaining a new access token for every request. - * + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class NoOpOAuthRememberMeServices implements OAuthRememberMeServices { public Map loadRememberedTokens(HttpServletRequest request, HttpServletResponse response) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/rememberme/OAuthRememberMeServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/rememberme/OAuthRememberMeServices.java index 905367c14..f68e45448 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/rememberme/OAuthRememberMeServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/rememberme/OAuthRememberMeServices.java @@ -9,8 +9,12 @@ /** * Services for "remembering" the access tokens that have been obtained. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthRememberMeServices { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/token/HttpSessionBasedTokenServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/token/HttpSessionBasedTokenServices.java index abfa1ce74..847a212c3 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/token/HttpSessionBasedTokenServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/token/HttpSessionBasedTokenServices.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -27,8 +27,12 @@ /** * Stores the tokens in an HTTP session. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class HttpSessionBasedTokenServices implements OAuthConsumerTokenServices { public static final String KEY_PREFIX = "OAUTH_TOKEN"; @@ -53,7 +57,7 @@ public void storeToken(String resourceId, OAuthConsumerToken token) { HttpSession session = getSession(); session.setAttribute(KEY_PREFIX + "#" + resourceId, token); - //adding support for oauth session extension (http://oauth.googlecode.com/svn/spec/ext/session/1.0/drafts/1/spec.html) + //adding support for oauth session extension (https://oauth.googlecode.com/svn/spec/ext/session/1.0/drafts/1/spec.html) Long expiration = null; String expiresInValue = token.getAdditionalParameters() != null ? token.getAdditionalParameters().get("oauth_expires_in") : null; if (expiresInValue != null) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/token/OAuthConsumerTokenServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/token/OAuthConsumerTokenServices.java index 854c205e7..40422998f 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/token/OAuthConsumerTokenServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/consumer/token/OAuthConsumerTokenServices.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,9 +21,13 @@ /** * Token services for an OAuth consumer. - * + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthConsumerTokenServices { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/BaseConsumerDetails.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/BaseConsumerDetails.java index 86abc37b4..003bfb096 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/BaseConsumerDetails.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/BaseConsumerDetails.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,18 +16,23 @@ package org.springframework.security.oauth.provider; -import org.springframework.security.oauth.common.signature.SignatureSecret; -import org.springframework.security.core.GrantedAuthority; - -import java.util.List; import java.util.ArrayList; +import java.util.List; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth.common.signature.SignatureSecret; /** * Base implementation for consumer details. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Andrew McCall */ +@SuppressWarnings("serial") +@Deprecated public class BaseConsumerDetails implements ResourceSpecificConsumerDetails, ExtraTrustConsumerDetails { private String consumerKey; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerAuthentication.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerAuthentication.java index 263b8b3b3..42597b46a 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerAuthentication.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerAuthentication.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -22,9 +22,14 @@ /** * Authentication for an OAuth consumer. - * + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class ConsumerAuthentication extends AbstractAuthenticationToken { private final ConsumerDetails consumerDetails; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerCredentials.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerCredentials.java index 236afd962..8c09f2926 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerCredentials.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerCredentials.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,8 +21,13 @@ /** * The credentials for an OAuth consumer request. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class ConsumerCredentials implements Serializable { private final String consumerKey; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerDetails.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerDetails.java index c90a1baf6..a72b18fb5 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerDetails.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerDetails.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -25,8 +25,12 @@ /** * Provides core OAuth consumer information. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface ConsumerDetails extends Serializable { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerDetailsService.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerDetailsService.java index a67415592..76031a77f 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerDetailsService.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ConsumerDetailsService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,8 +21,12 @@ /** * A service that provides the details about an oauth consumer. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface ConsumerDetailsService { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/DefaultAuthenticationHandler.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/DefaultAuthenticationHandler.java index d1ad40eda..dfda4d254 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/DefaultAuthenticationHandler.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/DefaultAuthenticationHandler.java @@ -9,8 +9,12 @@ /** * The default authentication handler. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class DefaultAuthenticationHandler implements OAuthAuthenticationHandler { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ExtraTrustConsumerDetails.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ExtraTrustConsumerDetails.java index d67d3e0f8..7ec33d13b 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ExtraTrustConsumerDetails.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ExtraTrustConsumerDetails.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,19 +19,22 @@ /** * Consumer details for a specific resource. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface ExtraTrustConsumerDetails extends ConsumerDetails { /** * Whether this consumer is required to obtain an authenticated oauth token. If true, it means that the OAuth consumer won't be granted access * to the protected resource unless the user is directed to the token authorization page. If false, it means that the provider has an additional * level of trust with the consumer. - *
- *
+ * * Not requiring an authenticated access token is also known as "2-legged" OAuth or "signed fetch". * * @return Whether this consumer is required to obtain an authenticated oauth token. */ boolean isRequiredToObtainAuthenticatedToken(); -} \ No newline at end of file +} diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/InMemoryConsumerDetailsService.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/InMemoryConsumerDetailsService.java index 4a153a0a9..32be99d0c 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/InMemoryConsumerDetailsService.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/InMemoryConsumerDetailsService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -24,8 +24,12 @@ /** * Basic, in-memory implementation of the consumer details service. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class InMemoryConsumerDetailsService implements ConsumerDetailsService { private Map consumerDetailsStore = new HashMap(); @@ -33,7 +37,7 @@ public class InMemoryConsumerDetailsService implements ConsumerDetailsService { public ConsumerDetails loadConsumerByConsumerKey(String consumerKey) throws OAuthException { ConsumerDetails details = consumerDetailsStore.get(consumerKey); if (details == null) { - throw new InvalidOAuthParametersException("Consumer not found: " + consumerKey); + throw new InvalidOAuthParametersException("Consumer not found"); } return details; } diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/InvalidOAuthParametersException.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/InvalidOAuthParametersException.java index e2e9f950f..c26d3e776 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/InvalidOAuthParametersException.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/InvalidOAuthParametersException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,13 @@ import org.springframework.security.oauth.common.OAuthException; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class InvalidOAuthParametersException extends OAuthException { public InvalidOAuthParametersException(String msg) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthAuthenticationDetails.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthAuthenticationDetails.java index d527146a0..c7a9d4de0 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthAuthenticationDetails.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthAuthenticationDetails.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -22,9 +22,14 @@ /** * Authentication details and includes the details of the OAuth consumer. - * + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class OAuthAuthenticationDetails extends WebAuthenticationDetails { private final ConsumerDetails consumerDetails; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthAuthenticationHandler.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthAuthenticationHandler.java index d0c277bb6..408a4b9c9 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthAuthenticationHandler.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthAuthenticationHandler.java @@ -8,8 +8,12 @@ /** * Callback interface for handing authentication details that are used when an authenticated request for a protected resource is received. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthAuthenticationHandler { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthProcessingFilterEntryPoint.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthProcessingFilterEntryPoint.java index 5dccae409..9ce9becc1 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthProcessingFilterEntryPoint.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthProcessingFilterEntryPoint.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -17,6 +17,7 @@ package org.springframework.security.oauth.provider; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth.common.signature.UnsupportedSignatureMethodException; import org.springframework.security.web.AuthenticationEntryPoint; import javax.servlet.ServletException; @@ -27,19 +28,31 @@ /** * Entry point for OAuth authentication requests. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class OAuthProcessingFilterEntryPoint implements AuthenticationEntryPoint { private String realmName; public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { - StringBuilder headerValue = new StringBuilder("OAuth"); - if (realmName != null) { - headerValue.append(" realm=\"").append(realmName).append('"'); - } - response.addHeader("WWW-Authenticate", headerValue.toString()); - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); + if (authException instanceof InvalidOAuthParametersException) { + response.sendError(400, authException.getMessage()); + } + else if (authException.getCause() instanceof UnsupportedSignatureMethodException) { + response.sendError(400, authException.getMessage()); + } + else { + StringBuilder headerValue = new StringBuilder("OAuth"); + if (realmName != null) { + headerValue.append(" realm=\"").append(realmName).append('"'); + } + response.addHeader("WWW-Authenticate", headerValue.toString()); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); + } } public String getRealmName() { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthProviderSupport.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthProviderSupport.java index f5b2af59e..6817bea5c 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthProviderSupport.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthProviderSupport.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,9 +21,13 @@ /** * Support logic for OAuth providers. - * + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthProviderSupport { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthVersionUnsupportedException.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthVersionUnsupportedException.java index 7fbabb0e2..aabf1722b 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthVersionUnsupportedException.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/OAuthVersionUnsupportedException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -17,8 +17,13 @@ package org.springframework.security.oauth.provider; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class OAuthVersionUnsupportedException extends InvalidOAuthParametersException { public OAuthVersionUnsupportedException(String msg) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ResourceSpecificConsumerDetails.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ResourceSpecificConsumerDetails.java index 0cffc2c05..474a5126a 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ResourceSpecificConsumerDetails.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/ResourceSpecificConsumerDetails.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,12 @@ /** * Consumer details for a specific resource. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface ResourceSpecificConsumerDetails extends ConsumerDetails { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerKeysAllowed.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerKeysAllowed.java index 8a95c59f6..5f70c138e 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerKeysAllowed.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerKeysAllowed.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -24,10 +24,14 @@ /** * The consumer keys that are allowed to access the specified method. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ @Target ( { ElementType.TYPE, ElementType.METHOD } ) @Retention ( RetentionPolicy.RUNTIME ) +@Deprecated public @interface ConsumerKeysAllowed { String[] value(); diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerRolesAllowed.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerRolesAllowed.java index 8ed5bb49e..9e727afc3 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerRolesAllowed.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerRolesAllowed.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -24,10 +24,14 @@ /** * The consumer roles that are allowed to access the specified method. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ @Target ( { ElementType.TYPE, ElementType.METHOD } ) @Retention ( RetentionPolicy.RUNTIME ) +@Deprecated public @interface ConsumerRolesAllowed { String[] value(); diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerSecurityConfig.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerSecurityConfig.java index 3caa13a6a..8d56db062 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerSecurityConfig.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerSecurityConfig.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -20,9 +20,14 @@ /** * Security config for consumer authorization of a method. - * + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class ConsumerSecurityConfig extends SecurityConfig { public static final ConsumerSecurityConfig DENY_ALL_ATTRIBUTE = new ConsumerSecurityConfig(DenyAllConsumers.class.getName(), null); diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerSecurityMetadataSource.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerSecurityMetadataSource.java index 2744a95da..16d3d5e13 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerSecurityMetadataSource.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerSecurityMetadataSource.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -27,9 +27,13 @@ import java.lang.annotation.Annotation; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Andrew McCall */ +@Deprecated public class ConsumerSecurityMetadataSource extends AbstractFallbackMethodSecurityMetadataSource { protected List findAttributes(Class clazz) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerSecurityVoter.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerSecurityVoter.java index ffc53105b..94ee09b8e 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerSecurityVoter.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/ConsumerSecurityVoter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -26,9 +26,13 @@ import java.util.Collection; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Andrew McCall */ +@Deprecated public class ConsumerSecurityVoter implements AccessDecisionVoter { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/DenyAllConsumers.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/DenyAllConsumers.java index 7b9709c82..9387c3c4b 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/DenyAllConsumers.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/DenyAllConsumers.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -24,9 +24,13 @@ /** * Annotation used to specify that a method is to be denied to all OAuth consumers. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ @Target ( { ElementType.TYPE, ElementType.METHOD } ) @Retention ( RetentionPolicy.RUNTIME ) +@Deprecated public @interface DenyAllConsumers { } diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/PermitAllConsumers.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/PermitAllConsumers.java index 99f8acc4f..11e705dba 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/PermitAllConsumers.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/attributes/PermitAllConsumers.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -25,9 +25,13 @@ * Annotation used to specify that a method is to be permitted to all OAuth consumers. Note that just because * a consumer is permitted, that doesn't mean that the user that the consumer is representing is permitted. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ @Target ( { ElementType.TYPE, ElementType.METHOD } ) @Retention ( RetentionPolicy.RUNTIME ) +@Deprecated public @interface PermitAllConsumers { } \ No newline at end of file diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/expression/OAuthMethodSecurityExpressionHandler.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/expression/OAuthMethodSecurityExpressionHandler.java index 44d20e173..0f6c2e14f 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/expression/OAuthMethodSecurityExpressionHandler.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/expression/OAuthMethodSecurityExpressionHandler.java @@ -19,9 +19,13 @@ import org.springframework.security.oauth.provider.OAuthAuthenticationDetails; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class OAuthMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler { @Override diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/AccessTokenProcessingFilter.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/AccessTokenProcessingFilter.java index 0a9bf8c42..52bc18003 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/AccessTokenProcessingFilter.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/AccessTokenProcessingFilter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -35,9 +35,13 @@ /** * Processing filter for handling a request for an OAuth access token. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Andrew McCall */ +@Deprecated public class AccessTokenProcessingFilter extends OAuthProviderProcessingFilter { // The OAuth spec doesn't specify a content-type of the response. However, it's NOT diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/CoreOAuthProviderSupport.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/CoreOAuthProviderSupport.java index b46b989aa..90ca0488b 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/CoreOAuthProviderSupport.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/CoreOAuthProviderSupport.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -31,8 +31,12 @@ /** * Utility for common logic for supporting an OAuth provider. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class CoreOAuthProviderSupport implements OAuthProviderSupport { private final Set supportedOAuthParameters; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/OAuthProviderProcessingFilter.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/OAuthProviderProcessingFilter.java index 3c3fa4bfa..aac199e34 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/OAuthProviderProcessingFilter.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/OAuthProviderProcessingFilter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -55,10 +55,14 @@ import java.util.Map; /** - * OAuth processing filter. This filter should be applied to requests for OAuth protected resources (see OAuth Core 1.0).

+ * OAuth processing filter. This filter should be applied to requests for OAuth protected resources (see OAuth Core 1.0). + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. * * @author Ryan Heaton */ +@Deprecated public abstract class OAuthProviderProcessingFilter implements Filter, InitializingBean, MessageSourceAware { /** @@ -117,7 +121,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo } log.debug(builder.toString()); } - + String consumerKey = oauthParams.get(OAuthConsumerParameter.oauth_consumer_key.toString()); if (consumerKey == null) { throw new InvalidOAuthParametersException(messages.getMessage("OAuthProcessingFilter.missingConsumerKey", "Missing consumer key.")); @@ -278,7 +282,7 @@ protected void validateSignature(ConsumerAuthentication authentication) throws A /** * Logic executed on valid signature. The security context can be assumed to hold a verified, authenticated - * {@link org.springframework.security.oauth.provider.ConsumerAuthentication}.

+ * {@link org.springframework.security.oauth.provider.ConsumerAuthentication} * * Default implementation continues the chain. * @@ -365,6 +369,8 @@ protected void onNewTimestamp() throws AuthenticationException { * @param request The request. * @param response The response. * @param failure The failure. + * @throws IOException thrown when there's an underlying IO exception + * @throws ServletException thrown in the case of an underlying Servlet exception */ protected void fail(HttpServletRequest request, HttpServletResponse response, AuthenticationException failure) throws IOException, ServletException { SecurityContextHolder.getContext().setAuthentication(null); @@ -373,15 +379,7 @@ protected void fail(HttpServletRequest request, HttpServletResponse response, Au log.debug(failure); } - if (failure instanceof InvalidOAuthParametersException) { - response.sendError(400, failure.getMessage()); - } - else if (failure.getCause() instanceof UnsupportedSignatureMethodException) { - response.sendError(400, failure.getMessage()); - } - else { - authenticationEntryPoint.commence(request, response, failure); - } + authenticationEntryPoint.commence(request, response, failure); } /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/ProtectedResourceProcessingFilter.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/ProtectedResourceProcessingFilter.java index cf7e5b5e0..6d6252192 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/ProtectedResourceProcessingFilter.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/ProtectedResourceProcessingFilter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -41,9 +41,13 @@ * load a different authentication request into the security context). If the protected resource is available * ONLY via OAuth access token, set ignoreMissingCredentials to false. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Andrew McCall */ +@Deprecated public class ProtectedResourceProcessingFilter extends OAuthProviderProcessingFilter { private boolean allowAllMethods = true; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/UnauthenticatedRequestTokenProcessingFilter.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/UnauthenticatedRequestTokenProcessingFilter.java index 3ac546d0d..9128a4cb8 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/UnauthenticatedRequestTokenProcessingFilter.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/UnauthenticatedRequestTokenProcessingFilter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -36,9 +36,13 @@ * Processing filter for handling a request for an OAuth token. The default implementation assumes a request for a new * unauthenticated request token. The default {@link #setFilterProcessesUrl(String) processes URL} is "/oauth_request_token". * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Andrew McCall */ +@Deprecated public class UnauthenticatedRequestTokenProcessingFilter extends OAuthProviderProcessingFilter { // The OAuth spec doesn't specify a content-type of the response. However, it's NOT diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/UserAuthorizationProcessingFilter.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/UserAuthorizationProcessingFilter.java index 7e42ab718..317a2ff13 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/UserAuthorizationProcessingFilter.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/UserAuthorizationProcessingFilter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -34,14 +34,18 @@ /** * Processing filter for handling a request to authenticate an OAuth request token. The default {@link #setFilterProcessesUrl(String) processes URL} - * is "/oauth_authenticate_token"

- *

+ * is "/oauth_authenticate_token". + * * This filter looks for one request parameter for the token id that is being authorized. The - * default name of the paramaters is "requestToken", but this can be configured.

- *

+ * default name of the paramaters is "requestToken", but this can be configured. + * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Andrew McCall */ +@Deprecated public class UserAuthorizationProcessingFilter extends AbstractAuthenticationProcessingFilter { protected static final String CALLBACK_ATTRIBUTE = UserAuthorizationProcessingFilter.class.getName() + "#CALLBACK"; @@ -76,12 +80,12 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ OAuthProviderToken token = getTokenServices().getToken(requestToken); if (token == null) { - throw new InvalidOAuthTokenException("No callback value has been provided for request token " + requestToken + "."); + throw new InvalidOAuthTokenException("No callback value has been provided for request token"); } String callbackURL = token.getCallbackUrl(); if (isRequire10a() && callbackURL == null) { - throw new InvalidOAuthTokenException("No callback value has been provided for request token " + requestToken + "."); + throw new InvalidOAuthTokenException("No callback value has been provided for request token"); } if (callbackURL != null) { @@ -172,4 +176,4 @@ public void setRequire10a(boolean require10a) { this.require10a = require10a; } -} \ No newline at end of file +} diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/UserAuthorizationSuccessfulAuthenticationHandler.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/UserAuthorizationSuccessfulAuthenticationHandler.java index 0135a396a..0b228ad7b 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/UserAuthorizationSuccessfulAuthenticationHandler.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/filter/UserAuthorizationSuccessfulAuthenticationHandler.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -37,8 +37,12 @@ * success URL. Otherwise, the oauth_verifier and oauth_token parmeters are appended to the callback URL and the user * is redirected. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Andrew McCall */ +@Deprecated public class UserAuthorizationSuccessfulAuthenticationHandler extends SimpleUrlAuthenticationSuccessHandler { private static Log LOG = LogFactory.getLog(UserAuthorizationSuccessfulAuthenticationHandler.class); diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/ExpiringTimestampNonceServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/ExpiringTimestampNonceServices.java index 9b10ade5e..ed53dfd64 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/ExpiringTimestampNonceServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/ExpiringTimestampNonceServices.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -22,14 +22,18 @@ /** * Nonce services that only validates the timestamp of a consumer request. The nonce - * is not checked for replay attacks.

+ * is not checked for replay attacks. * * The timestamp is interpreted as the number of seconds from January 1, 1970 00:00:00 GMT. If the timestamp * is older than the configured validity window, the nonce is not valid. The default validity window is * 12 hours. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class ExpiringTimestampNonceServices implements OAuthNonceServices { private long validityWindowSeconds = 60 * 60 * 12; //we'll default to a 12-hour validity window. diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/InMemoryNonceServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/InMemoryNonceServices.java index cae26e37a..1548017b8 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/InMemoryNonceServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/InMemoryNonceServices.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -24,9 +24,8 @@ /** * Expands on the {@link org.springframework.security.oauth.provider.nonce.ExpiringTimestampNonceServices} to include - * validation of the nonce for replay protection.
- *
- * + * validation of the nonce for replay protection. + * * To validate the nonce, the InMemoryNonceService first validates the consumer key and timestamp as does the * {@link org.springframework.security.oauth.provider.nonce.ExpiringTimestampNonceServices}. Assuming the consumer and * timestamp are valid, the InMemoryNonceServices further ensures that the specified nonce was not used with the @@ -38,9 +37,13 @@ * this class has a per request memory overhead. Keeping the validity window short helps prevent wasting a lot of * memory. 10 minutes that allows for minor variations in time between servers. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton * @author Jilles van Gurp */ +@Deprecated public class InMemoryNonceServices implements OAuthNonceServices { /** @@ -62,7 +65,7 @@ public void validateNonce(ConsumerDetails consumerDetails, long timestamp, Strin synchronized (NONCES) { if (NONCES.contains(entry)) { - throw new NonceAlreadyUsedException("Nonce already used: " + nonce); + throw new NonceAlreadyUsedException("Nonce already used"); } else { NONCES.add(entry); diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/NonceAlreadyUsedException.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/NonceAlreadyUsedException.java index dd9008027..d728bbfe7 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/NonceAlreadyUsedException.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/NonceAlreadyUsedException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,13 @@ import org.springframework.security.oauth.common.OAuthException; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class NonceAlreadyUsedException extends OAuthException { public NonceAlreadyUsedException(String msg) { super(msg); diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/NullNonceServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/NullNonceServices.java index 0b2719f07..6e8d86a03 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/NullNonceServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/NullNonceServices.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -23,8 +23,12 @@ * No-op nonce services. Assumes all nonces are valid. This leaves the provider exposed to the dangers * of an unlimited timestamp validity window and OAuth request replay attacks. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class NullNonceServices implements OAuthNonceServices { public void validateNonce(ConsumerDetails consumerDetails, long timestamp, String nonce) throws AuthenticationException { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/OAuthNonceServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/OAuthNonceServices.java index 91611fc62..62de9e723 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/OAuthNonceServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/nonce/OAuthNonceServices.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -20,8 +20,12 @@ import org.springframework.security.oauth.provider.ConsumerDetails; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthNonceServices { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/ExpiredOAuthTokenException.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/ExpiredOAuthTokenException.java index 4e290813b..0eaa0a9c7 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/ExpiredOAuthTokenException.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/ExpiredOAuthTokenException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,13 @@ import org.springframework.security.oauth.common.OAuthException; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class ExpiredOAuthTokenException extends OAuthException { public ExpiredOAuthTokenException(String msg) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/InMemoryProviderTokenServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/InMemoryProviderTokenServices.java index f68b2e420..d73132a5d 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/InMemoryProviderTokenServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/InMemoryProviderTokenServices.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,8 +21,12 @@ /** * Implementation of TokenServices that stores tokens in memory. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class InMemoryProviderTokenServices extends RandomValueProviderTokenServices { protected final ConcurrentHashMap tokenStore = new ConcurrentHashMap(); diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/InMemorySelfCleaningProviderTokenServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/InMemorySelfCleaningProviderTokenServices.java index 3142eaf9b..45e9c3baf 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/InMemorySelfCleaningProviderTokenServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/InMemorySelfCleaningProviderTokenServices.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -27,8 +27,12 @@ /** * Implementation of TokenServices that stores tokens in memory. The token services will schedule a thread to do cleaning up of expired tokens. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class InMemorySelfCleaningProviderTokenServices extends InMemoryProviderTokenServices implements DisposableBean { private ScheduledExecutorService scheduler; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/InvalidOAuthTokenException.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/InvalidOAuthTokenException.java index 7de28f1ab..7874c8b14 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/InvalidOAuthTokenException.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/InvalidOAuthTokenException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,13 @@ import org.springframework.security.oauth.common.OAuthException; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class InvalidOAuthTokenException extends OAuthException { public InvalidOAuthTokenException(String msg) { diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthAccessProviderToken.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthAccessProviderToken.java index 858ae43e6..b8cbc9858 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthAccessProviderToken.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthAccessProviderToken.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,12 @@ import org.springframework.security.core.Authentication; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthAccessProviderToken extends OAuthProviderToken { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthProviderToken.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthProviderToken.java index 15b0f3922..375761ad5 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthProviderToken.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthProviderToken.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,12 @@ import java.io.Serializable; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthProviderToken extends Serializable { /** * The value of the token. diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthProviderTokenImpl.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthProviderTokenImpl.java index 8a5c24a86..3a9916897 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthProviderTokenImpl.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthProviderTokenImpl.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,8 +21,12 @@ /** * Basic implementation for an OAuth token. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class OAuthProviderTokenImpl implements OAuthAccessProviderToken { private static final long serialVersionUID = -1794426591002744140L; diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthProviderTokenServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthProviderTokenServices.java index b345a9bbd..db8f42a6b 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthProviderTokenServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthProviderTokenServices.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -20,8 +20,12 @@ import org.springframework.security.core.AuthenticationException; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthProviderTokenServices { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthTokenLifecycleListener.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthTokenLifecycleListener.java index a09738624..375ba701c 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthTokenLifecycleListener.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthTokenLifecycleListener.java @@ -3,8 +3,12 @@ /** * Interface for listening to the lifecycle of a token. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthTokenLifecycleListener { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthTokenLifecycleRegistry.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthTokenLifecycleRegistry.java index 16a46a7bb..f5906a084 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthTokenLifecycleRegistry.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/OAuthTokenLifecycleRegistry.java @@ -7,8 +7,12 @@ /** * Interface for a registry of token lifecycle listeners. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthTokenLifecycleRegistry { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/RandomValueProviderTokenServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/RandomValueProviderTokenServices.java index e0ed98ad2..67218cd99 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/RandomValueProviderTokenServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/token/RandomValueProviderTokenServices.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -27,14 +27,17 @@ /** * Base implementation for token services that uses random values to generate tokens. Only the persistence mechanism - * is left unimplemented.

+ * is left unimplemented. * * This base implementation creates tokens that have an expiration. For request tokens, the default validity is - * 10 minutes. For access tokens, the default validity is 12 hours.

+ * 10 minutes. For access tokens, the default validity is 12 hours. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. * * @author Ryan Heaton */ +@Deprecated public abstract class RandomValueProviderTokenServices implements OAuthProviderTokenServices, InitializingBean, OAuthTokenLifecycleRegistry { private Random random; @@ -70,7 +73,6 @@ public abstract class RandomValueProviderTokenServices implements OAuthProviderT /** * Initialze these token services. If no random generator is set, one will be created. * - * @throws Exception */ public void afterPropertiesSet() throws Exception { if (random == null) { @@ -82,7 +84,7 @@ public OAuthProviderToken getToken(String token) throws AuthenticationException OAuthProviderTokenImpl tokenImpl = readToken(token); if (tokenImpl == null) { - throw new InvalidOAuthTokenException("Invalid token: " + token); + throw new InvalidOAuthTokenException("Invalid token"); } else if (isExpired(tokenImpl)) { removeToken(token); @@ -97,7 +99,7 @@ else if (isExpired(tokenImpl)) { * Whether the auth token is expired. * * @param authToken The auth token to check for expiration. - * @return Whether the auth token is expired. + * @return Whether the auth token is expired. */ protected boolean isExpired(OAuthProviderTokenImpl authToken) { if (authToken.isAccessToken()) { @@ -136,7 +138,7 @@ public void authorizeRequestToken(String requestToken, String verifier, Authenti OAuthProviderTokenImpl tokenImpl = readToken(requestToken); if (tokenImpl == null) { - throw new InvalidOAuthTokenException("Invalid token: " + requestToken); + throw new InvalidOAuthTokenException("Invalid token"); } else if (isExpired(tokenImpl)) { removeToken(requestToken); @@ -157,7 +159,7 @@ public OAuthAccessProviderToken createAccessToken(String requestToken) throws Au OAuthProviderTokenImpl tokenImpl = readToken(requestToken); if (tokenImpl == null) { - throw new InvalidOAuthTokenException("Invalid token: " + requestToken); + throw new InvalidOAuthTokenException("Invalid token"); } else if (isExpired(tokenImpl)) { removeToken(requestToken); diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/verifier/OAuthVerifierServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/verifier/OAuthVerifierServices.java index a775b9449..020ba3a5d 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/verifier/OAuthVerifierServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/verifier/OAuthVerifierServices.java @@ -3,8 +3,12 @@ /** * Service for generating a verifier. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public interface OAuthVerifierServices { /** diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/verifier/RandomValueVerifierServices.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/verifier/RandomValueVerifierServices.java index db3f26040..a163cec47 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/verifier/RandomValueVerifierServices.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/verifier/RandomValueVerifierServices.java @@ -8,8 +8,12 @@ /** * Basic implementation of the verifier services that creates a random-value verifier and stores it in an in-memory map. * + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@Deprecated public class RandomValueVerifierServices implements OAuthVerifierServices, InitializingBean { private static final char[] DEFAULT_CODEC = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray(); diff --git a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/verifier/VerificationFailedException.java b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/verifier/VerificationFailedException.java index aa240beb3..986218f40 100644 --- a/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/verifier/VerificationFailedException.java +++ b/spring-security-oauth/src/main/java/org/springframework/security/oauth/provider/verifier/VerificationFailedException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,8 +19,13 @@ import org.springframework.security.oauth.common.OAuthException; /** + *

+ * @deprecated The OAuth 1.0 Protocol RFC 5849 is obsoleted by the OAuth 2.0 Authorization Framework RFC 6749. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class VerificationFailedException extends OAuthException { public VerificationFailedException(String msg) { super(msg); diff --git a/spring-security-oauth/src/main/resources/META-INF/spring.schemas b/spring-security-oauth/src/main/resources/META-INF/spring.schemas index 32f6b1546..0a3b46ad4 100644 --- a/spring-security-oauth/src/main/resources/META-INF/spring.schemas +++ b/spring-security-oauth/src/main/resources/META-INF/spring.schemas @@ -1,2 +1,2 @@ -http\://www.springframework.org/schema/security/spring-security-oauth-1.0.xsd=org/springframework/security/oauth/spring-security-oauth-1.0.xsd -http\://www.springframework.org/schema/security/spring-security-oauth.xsd=org/springframework/security/oauth/spring-security-oauth-1.0.xsd +https\://www.springframework.org/schema/security/spring-security-oauth-1.0.xsd=org/springframework/security/oauth/spring-security-oauth-1.0.xsd +https\://www.springframework.org/schema/security/spring-security-oauth.xsd=org/springframework/security/oauth/spring-security-oauth-1.0.xsd diff --git a/spring-security-oauth/src/main/resources/org/springframework/security/oauth/spring-security-oauth-1.0.xsd b/spring-security-oauth/src/main/resources/org/springframework/security/oauth/spring-security-oauth-1.0.xsd index 45a817dcd..e71aedc22 100644 --- a/spring-security-oauth/src/main/resources/org/springframework/security/oauth/spring-security-oauth-1.0.xsd +++ b/spring-security-oauth/src/main/resources/org/springframework/security/oauth/spring-security-oauth-1.0.xsd @@ -257,7 +257,7 @@ - Element for declaring and configuring an expression handler for oauth security expressions. See http://static.springsource.org/spring-security/site/docs/3.0.x/reference/el-access.html + Element for declaring and configuring an expression handler for oauth security expressions. See https://docs.spring.io/spring-security/site/docs/4.0.x/reference/html/el-access.html diff --git a/spring-security-oauth/src/test/java/net/oauth/signature/TestGoogleCodeCompatibility.java b/spring-security-oauth/src/test/java/net/oauth/signature/GoogleCodeCompatibilityTests.java similarity index 94% rename from spring-security-oauth/src/test/java/net/oauth/signature/TestGoogleCodeCompatibility.java rename to spring-security-oauth/src/test/java/net/oauth/signature/GoogleCodeCompatibilityTests.java index 4aa19520b..3db9d5333 100644 --- a/spring-security-oauth/src/test/java/net/oauth/signature/TestGoogleCodeCompatibility.java +++ b/spring-security-oauth/src/test/java/net/oauth/signature/GoogleCodeCompatibilityTests.java @@ -1,18 +1,18 @@ -/* +/** * Copyright 2008 Web Cohesion * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 net.oauth.signature; @@ -43,7 +43,7 @@ * @author Dave Syer */ @RunWith(MockitoJUnitRunner.class) -public class TestGoogleCodeCompatibility { +public class GoogleCodeCompatibilityTests { @Mock private HttpServletRequest request; @@ -87,13 +87,13 @@ protected String getBaseUrl(HttpServletRequest request) { when(request.getParameterValues(param.getKey())).thenReturn(param.getValue()); } - String header = "OAuth realm=\"http://sp.example.com//"," + String header = "OAuth realm=\"https://sp.example.com//"," + " oauth_consumer_key=\"0685bd9184jfhq22\"," + " oauth_token=\"ad180jjd733klru7\"," + " oauth_signature_method=\"HMAC-SHA1\"," + " oauth_signature=\"wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D\"," + " oauth_timestamp=\"137131200\"," + " oauth_callback=\"" - + OAuthCodec.oauthEncode("http://myhost.com/callback") + "\"," + + OAuthCodec.oauthEncode("https://myhost.com/callback") + "\"," + " oauth_nonce=\"4572616e48616d6d65724c61686176\"," + " oauth_version=\"1.0\""; when(request.getHeaders("Authorization")).thenReturn(Collections.enumeration(Arrays.asList(header))); diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/TestOAuthCodec.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/OAuthCodecTests.java similarity index 93% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/common/TestOAuthCodec.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/common/OAuthCodecTests.java index f1be46169..cc55abddf 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/TestOAuthCodec.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/OAuthCodecTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -24,7 +24,7 @@ /** * @author Ryan Heaton */ -public class TestOAuthCodec { +public class OAuthCodecTests { /** * tests idempotent decode. diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/TestCoreOAuthSignatureMethodFactory.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/CoreOAuthSignatureMethodFactoryTests.java similarity index 97% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/TestCoreOAuthSignatureMethodFactory.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/CoreOAuthSignatureMethodFactoryTests.java index c0bf823b8..107277d77 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/TestCoreOAuthSignatureMethodFactory.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/CoreOAuthSignatureMethodFactoryTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -32,7 +32,7 @@ /** * @author Ryan Heaton */ -public class TestCoreOAuthSignatureMethodFactory { +public class CoreOAuthSignatureMethodFactoryTests { /** * tests getting the signature method. diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/TestHMAC_SHA1SignatureMethod.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/HMAC_SHA1SignatureMethodTests.java similarity index 96% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/TestHMAC_SHA1SignatureMethod.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/HMAC_SHA1SignatureMethodTests.java index ccb2c60f1..0ebf3994b 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/TestHMAC_SHA1SignatureMethod.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/HMAC_SHA1SignatureMethodTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -25,7 +25,7 @@ /** * @author Ryan Heaton */ -public class TestHMAC_SHA1SignatureMethod { +public class HMAC_SHA1SignatureMethodTests { /** * Test sign and verify. diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/TestPlainTextSignatureMethod.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/PlainTextSignatureMethodTests.java similarity index 92% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/TestPlainTextSignatureMethod.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/PlainTextSignatureMethodTests.java index c0b601036..fdcc840f5 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/TestPlainTextSignatureMethod.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/PlainTextSignatureMethodTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,7 +21,7 @@ /** * @author Ryan Heaton */ -public class TestPlainTextSignatureMethod { +public class PlainTextSignatureMethodTests { /** * tests signing and verifying. diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/TestRSA_SHA1SignatureMethod.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/RSA_SHA1SignatureMethodTests.java similarity index 97% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/TestRSA_SHA1SignatureMethod.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/RSA_SHA1SignatureMethodTests.java index 6c118a547..e68dd10ad 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/TestRSA_SHA1SignatureMethod.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/RSA_SHA1SignatureMethodTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -31,7 +31,7 @@ /** * @author Ryan Heaton */ -public class TestRSA_SHA1SignatureMethod { +public class RSA_SHA1SignatureMethodTests { /** * tests signing and verifying. diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/config/TestAuthorizationServerBeanDefinitionParser.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/config/AuthorizationServerBeanDefinitionParserTests.java similarity index 89% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/config/TestAuthorizationServerBeanDefinitionParser.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/config/AuthorizationServerBeanDefinitionParserTests.java index e69660ac3..322a0bad2 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/config/TestAuthorizationServerBeanDefinitionParser.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/config/AuthorizationServerBeanDefinitionParserTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,17 +16,13 @@ package org.springframework.security.oauth.config; -import java.io.IOException; -import java.lang.reflect.Field; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import java.lang.reflect.Field; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth.provider.filter.UserAuthorizationProcessingFilter; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; @@ -36,12 +32,9 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.ReflectionUtils; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) -public class TestAuthorizationServerBeanDefinitionParser { +public class AuthorizationServerBeanDefinitionParserTests { @Autowired private UserAuthorizationProcessingFilter filter; diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/config/TestConsumerServiceBeanDefinitionParser.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/config/ConsumerServiceBeanDefinitionParserTests.java similarity index 95% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/config/TestConsumerServiceBeanDefinitionParser.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/config/ConsumerServiceBeanDefinitionParserTests.java index ce91e65f5..cc32cfa44 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/config/TestConsumerServiceBeanDefinitionParser.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/config/ConsumerServiceBeanDefinitionParserTests.java @@ -14,7 +14,7 @@ @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) -public class TestConsumerServiceBeanDefinitionParser { +public class ConsumerServiceBeanDefinitionParserTests { @Autowired private ConsumerDetailsService clientDetailsService; diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/config/TestFilterChainInitialization.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/config/FilterChainInitializationTests.java similarity index 95% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/config/TestFilterChainInitialization.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/config/FilterChainInitializationTests.java index 96f1b7762..5aa8ed3d5 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/config/TestFilterChainInitialization.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/config/FilterChainInitializationTests.java @@ -14,7 +14,7 @@ @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) -public class TestFilterChainInitialization { +public class FilterChainInitializationTests { @Autowired private ConsumerDetailsService clientDetailsService; diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/TestGoogleOAuth.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/GoogleOAuthTests.java similarity index 90% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/TestGoogleOAuth.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/GoogleOAuthTests.java index e408b515f..39ff26db4 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/TestGoogleOAuth.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/GoogleOAuthTests.java @@ -11,7 +11,7 @@ /** * @author Ryan Heaton */ -public class TestGoogleOAuth { +public class GoogleOAuthTests { /** * tests getting a request token. @@ -32,14 +32,14 @@ public void testGetRequestToken() throws Exception { googleDetails.setSignatureMethod(HMAC_SHA1SignatureMethod.SIGNATURE_NAME); googleDetails.setRequestTokenHttpMethod("GET"); HashMap additional = new HashMap(); - additional.put("scope", "http://picasaweb.google.com/data"); + additional.put("scope", "https://picasaweb.google.com/data"); googleDetails.setAdditionalParameters(additional); detailsStore.put(googleDetails.getId(), googleDetails); service.setResourceDetailsStore(detailsStore); support.setProtectedResourceDetailsService(service); // uncomment to see a request to google. - // see http://code.google.com/apis/accounts/docs/OAuth_ref.html - // and http://jira.codehaus.org/browse/OAUTHSS-37 + // see https://code.google.com/apis/accounts/docs/OAuth_ref.html + // and https://jira.codehaus.org/browse/OAUTHSS-37 // OAuthConsumerToken token = support.getUnauthorizedRequestToken("google", "urn:mycallback"); // System.out.println(token.getValue()); // System.out.println(token.getSecret()); diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/client/TestCoreOAuthConsumerSupport.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/client/CoreOAuthConsumerSupportTests.java similarity index 90% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/client/TestCoreOAuthConsumerSupport.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/client/CoreOAuthConsumerSupportTests.java index 8a2fddd6e..bf4d76d51 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/client/TestCoreOAuthConsumerSupport.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/client/CoreOAuthConsumerSupportTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -38,8 +38,10 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -67,7 +69,7 @@ */ @SuppressWarnings("restriction") @RunWith(MockitoJUnitRunner.class) -public class TestCoreOAuthConsumerSupport { +public class CoreOAuthConsumerSupportTests { @Mock private ProtectedResourceDetails details; @@ -79,7 +81,8 @@ public void testAfterPropertiesSet() throws Exception { try { new CoreOAuthConsumerSupport().afterPropertiesSet(); fail("should required a protected resource details service."); - } catch (IllegalArgumentException e) { + } + catch (IllegalArgumentException e) { } } @@ -90,7 +93,7 @@ public void testAfterPropertiesSet() throws Exception { public void testReadResouce() throws Exception { OAuthConsumerToken token = new OAuthConsumerToken(); - URL url = new URL("http://myhost.com/resource?with=some&query=params&too"); + URL url = new URL("https://myhost.com/resource?with=some&query=params&too"); final ConnectionProps connectionProps = new ConnectionProps(); final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[0]); @@ -146,7 +149,8 @@ public URL configureURLForProtectedAccess(URL url, OAuthConsumerToken accessToke try { return new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile(), new StreamHandlerForTestingPurposes(connectionMock)); - } catch (MalformedURLException e) { + } + catch (MalformedURLException e) { throw new RuntimeException(e); } } @@ -165,7 +169,8 @@ public String getOAuthQueryString(ProtectedResourceDetails details, OAuthConsume try { support.readResource(details, url, "POST", token, null, null); fail("shouldn't have been a valid response code."); - } catch (OAuthRequestFailedException e) { + } + catch (OAuthRequestFailedException e) { // fall through... } assertFalse(connectionProps.doOutput); @@ -181,7 +186,8 @@ public String getOAuthQueryString(ProtectedResourceDetails details, OAuthConsume try { support.readResource(details, url, "POST", token, null, null); fail("shouldn't have been a valid response code."); - } catch (OAuthRequestFailedException e) { + } + catch (OAuthRequestFailedException e) { // fall through... } assertFalse(connectionProps.doOutput); @@ -198,7 +204,8 @@ public String getOAuthQueryString(ProtectedResourceDetails details, OAuthConsume try { support.readResource(details, url, "POST", token, null, null); fail("shouldn't have been a valid response code."); - } catch (InvalidOAuthRealmException e) { + } + catch (InvalidOAuthRealmException e) { // fall through... } assertFalse(connectionProps.doOutput); @@ -400,8 +407,8 @@ protected String getSignatureBaseString(Map> oauthPara when(details.getSignatureMethod()).thenReturn(HMAC_SHA1SignatureMethod.SIGNATURE_NAME); SharedConsumerSecret secret = new SharedConsumerSecretImpl("shh!!!"); when(details.getSharedSecret()).thenReturn(secret); - when(sigFactory.getSignatureMethod(HMAC_SHA1SignatureMethod.SIGNATURE_NAME, secret, null)).thenReturn( - sigMethod); + when(sigFactory.getSignatureMethod(HMAC_SHA1SignatureMethod.SIGNATURE_NAME, secret, null)) + .thenReturn(sigMethod); when(sigMethod.sign("MYSIGBASESTRING")).thenReturn("MYSIGNATURE"); Map> params = support.loadOAuthParameters(details, url, token, "POST", null); @@ -439,13 +446,40 @@ public void testGetSignatureBaseString() throws Exception { CoreOAuthConsumerSupport support = new CoreOAuthConsumerSupport(); - String baseString = support.getSignatureBaseString(oauthParams, new URL("http://photos.example.net/photos"), + String baseString = support.getSignatureBaseString(oauthParams, new URL("https://photos.example.net/photos"), "geT"); assertEquals( - "GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal", + "GET&https%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal", baseString); } + @Test + public void testGetSignatureBaseStringSimple() throws Exception { + Map> oauthParams = new HashMap>(); + oauthParams.put("foo", Collections.singleton((CharSequence) "bar")); + oauthParams.put("bar", new LinkedHashSet(Arrays. asList("120", "24"))); + + CoreOAuthConsumerSupport support = new CoreOAuthConsumerSupport(); + + String baseString = support.getSignatureBaseString(oauthParams, new URL("https://photos.example.net/photos"), + "get"); + assertEquals("GET&https%3A%2F%2Fphotos.example.net%2Fphotos&bar%3D120%26bar%3D24%26foo%3Dbar", baseString); + } + + // See SECOAUTH-383 + @Test + public void testGetSignatureBaseStringMultivaluedLast() throws Exception { + Map> oauthParams = new HashMap>(); + oauthParams.put("foo", Collections.singleton((CharSequence) "bar")); + oauthParams.put("pin", new LinkedHashSet(Arrays. asList("2", "1"))); + + CoreOAuthConsumerSupport support = new CoreOAuthConsumerSupport(); + + String baseString = support.getSignatureBaseString(oauthParams, new URL("https://photos.example.net/photos"), + "get"); + assertEquals("GET&https%3A%2F%2Fphotos.example.net%2Fphotos&foo%3Dbar%26pin%3D1%26pin%3D2", baseString); + } + static class StreamHandlerForTestingPurposes extends Handler { private final HttpURLConnectionForTestingPurposes connection; @@ -469,7 +503,7 @@ static class HttpURLConnectionForTestingPurposes extends HttpURLConnection { /** * Constructor for the HttpURLConnection. - * + * * @param u the URL */ public HttpURLConnectionForTestingPurposes(URL u) { @@ -490,11 +524,17 @@ public void connect() throws IOException { static class ConnectionProps { public int responseCode; + public String responseMessage; + public String method; + public Boolean doOutput; + public Boolean connected; + public OutputStream outputStream; + public final Map headerFields = new TreeMap(); public void reset() { diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/client/OAuthRestTemplateTests.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/client/OAuthRestTemplateTests.java new file mode 100644 index 000000000..2605a37b3 --- /dev/null +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/client/OAuthRestTemplateTests.java @@ -0,0 +1,96 @@ +package org.springframework.security.oauth.consumer.client; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.http.MediaType; +import org.springframework.security.oauth.common.signature.SharedConsumerSecretImpl; +import org.springframework.security.oauth.consumer.ProtectedResourceDetails; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.test.web.client.RequestMatcher; + +import java.util.Collections; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +@RunWith(MockitoJUnitRunner.class) +public class OAuthRestTemplateTests { + + @Mock + private ProtectedResourceDetails details; + + @Test + public void testOAuthRestTemplateNoAdditionalParameters() { + String url = "https://myhost.com/resource?with=some&query=params&too"; + + when(details.getSignatureMethod()).thenReturn("HMAC-SHA1"); + when(details.getConsumerKey()).thenReturn("consumerKey"); + when(details.getSharedSecret()).thenReturn(new SharedConsumerSecretImpl("consumerSecret")); + when(details.getAuthorizationHeaderRealm()).thenReturn("realm"); + when(details.isAcceptsAuthorizationHeader()).thenReturn(true); + when(details.getAdditionalRequestHeaders()).thenReturn(null); + when(details.getAdditionalParameters()).thenReturn(null); + + OAuthRestTemplate restTemplate = new OAuthRestTemplate(details); + + MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate); + mockServer + .expect(requestTo(url)) + .andExpect(method(POST)) + .andExpect(headerContains("Authorization", "OAuth realm=\"realm\"")) + .andExpect(headerContains("Authorization", "oauth_consumer_key=\"consumerKey\"")) + .andExpect(headerDoesNotContain("Authorization", "oauth_token")) + .andRespond(withSuccess("{}", MediaType.APPLICATION_JSON)); + + assertThat(restTemplate.getRequestFactory(), is(instanceOf(OAuthClientHttpRequestFactory.class))); + assertTrue(((OAuthClientHttpRequestFactory) restTemplate.getRequestFactory()).getAdditionalOAuthParameters().isEmpty()); + assertThat(restTemplate.postForObject(url, "foo", String.class), is(equalTo("{}"))); + } + + @Test + public void testOAuthRestTemplateWithAdditionalParameters() { + String url = "https://myhost.com/resource?with=some&query=params&too"; + + when(details.getSignatureMethod()).thenReturn("HMAC-SHA1"); + when(details.getConsumerKey()).thenReturn("consumerKey"); + when(details.getSharedSecret()).thenReturn(new SharedConsumerSecretImpl("consumerSecret")); + when(details.getAuthorizationHeaderRealm()).thenReturn("realm"); + when(details.isAcceptsAuthorizationHeader()).thenReturn(true); + when(details.getAdditionalRequestHeaders()).thenReturn(null); + when(details.getAdditionalParameters()).thenReturn(Collections.singletonMap("oauth_token", "")); + + OAuthRestTemplate restTemplate = new OAuthRestTemplate(details); + + MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate); + mockServer + .expect(requestTo(url)) + .andExpect(method(POST)) + .andExpect(headerContains("Authorization", "OAuth realm=\"realm\"")) + .andExpect(headerContains("Authorization", "oauth_consumer_key=\"consumerKey\"")) + .andExpect(headerContains("Authorization", "oauth_token=\"\"")) + .andRespond(withSuccess("{}", MediaType.APPLICATION_JSON)); + + assertThat(restTemplate.getRequestFactory(), is(instanceOf(OAuthClientHttpRequestFactory.class))); + Map additionalOAuthParameters = ((OAuthClientHttpRequestFactory) restTemplate.getRequestFactory()).getAdditionalOAuthParameters(); + assertTrue(additionalOAuthParameters.containsKey("oauth_token")); + assertTrue(additionalOAuthParameters.get("oauth_token").isEmpty()); + assertThat(restTemplate.postForObject(url, "foo", String.class), is(equalTo("{}"))); + } + + private RequestMatcher headerContains(String name, String substring) { + return header(name, containsString(substring)); + } + + private RequestMatcher headerDoesNotContain(String name, String substring) { + return header(name, not(containsString(substring))); + } + +} \ No newline at end of file diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/filter/TestOAuthConsumerContextFilter.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/filter/OAuthConsumerContextFilterTests.java similarity index 91% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/filter/TestOAuthConsumerContextFilter.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/filter/OAuthConsumerContextFilterTests.java index a26a7867e..c531574f2 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/filter/TestOAuthConsumerContextFilter.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/filter/OAuthConsumerContextFilterTests.java @@ -36,7 +36,7 @@ * @author Ryan Heaton */ @RunWith(MockitoJUnitRunner.class) -public class TestOAuthConsumerContextFilter { +public class OAuthConsumerContextFilterTests { @Mock private ProtectedResourceDetails details; @Mock @@ -60,15 +60,15 @@ public void testGetUserAuthorizationRedirectURL() throws Exception { OAuthConsumerToken token = new OAuthConsumerToken(); token.setResourceId("resourceId"); token.setValue("mytoken"); - when(details.getUserAuthorizationURL()).thenReturn("http://user-auth/context?with=some&queryParams"); + when(details.getUserAuthorizationURL()).thenReturn("https://user-auth/context?with=some&queryParams"); when(details.isUse10a()).thenReturn(false); assertEquals( - "http://user-auth/context?with=some&queryParams&oauth_token=mytoken&oauth_callback=urn%3A%2F%2Fcallback%3Fwith%3Dsome%26query%3Dparams", + "https://user-auth/context?with=some&queryParams&oauth_token=mytoken&oauth_callback=urn%3A%2F%2Fcallback%3Fwith%3Dsome%26query%3Dparams", filter.getUserAuthorizationRedirectURL(details, token, "urn://callback?with=some&query=params")); - when(details.getUserAuthorizationURL()).thenReturn("http://user-auth/context?with=some&queryParams"); + when(details.getUserAuthorizationURL()).thenReturn("https://user-auth/context?with=some&queryParams"); when(details.isUse10a()).thenReturn(true); - assertEquals("http://user-auth/context?with=some&queryParams&oauth_token=mytoken", + assertEquals("https://user-auth/context?with=some&queryParams&oauth_token=mytoken", filter.getUserAuthorizationRedirectURL(details, token, "urn://callback?with=some&query=params")); } diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/rememberme/HttpSessionOAuthRememberMeServicesTests.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/rememberme/HttpSessionOAuthRememberMeServicesTests.java new file mode 100644 index 000000000..b20fa8e57 --- /dev/null +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/consumer/rememberme/HttpSessionOAuthRememberMeServicesTests.java @@ -0,0 +1,136 @@ +/* + * Copyright 2008 Web Cohesion + * + * 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.oauth.consumer.rememberme; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.oauth.consumer.OAuthConsumerToken; + +/** + * @author Alex Rau + */ +public class HttpSessionOAuthRememberMeServicesTests { + + @Test + public void testEmptySession() { + + MockHttpSession mockHttpSession = new MockHttpSession(); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + request.setSession(mockHttpSession); + + HttpSessionOAuthRememberMeServices oAuthRememberMeService = new HttpSessionOAuthRememberMeServices(); + + Map tokens = oAuthRememberMeService.loadRememberedTokens(request, response); + + Assert.assertNull(tokens); + + } + + @Test + public void testNoTokensRemembered() { + + MockHttpSession mockHttpSession = new MockHttpSession(); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + request.setSession(mockHttpSession); + + HttpSessionOAuthRememberMeServices oAuthRememberMeService = new HttpSessionOAuthRememberMeServices(); + + Map tokens = new HashMap(); + + oAuthRememberMeService.rememberTokens(tokens, request, response); + + Assert.assertEquals(0, oAuthRememberMeService.loadRememberedTokens(request, response).size()); + + } + + @Test + public void testStoreEverything() { + + MockHttpSession mockHttpSession = new MockHttpSession(); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + request.setSession(mockHttpSession); + + HttpSessionOAuthRememberMeServices oAuthRememberMeService = new HttpSessionOAuthRememberMeServices(); + + Map tokens = new HashMap(); + + { + OAuthConsumerToken token = new OAuthConsumerToken(); + token.setAccessToken(false); + tokens.put("resourceID1", token); + } + + { + OAuthConsumerToken token = new OAuthConsumerToken(); + token.setAccessToken(true); + tokens.put("resourceID2", token); + } + + oAuthRememberMeService.rememberTokens(tokens, request, response); + + Assert.assertEquals(1, oAuthRememberMeService.loadRememberedTokens(request, response).size()); + + } + + @Test + public void testStoreRequestTokensOnly() { + + MockHttpSession mockHttpSession = new MockHttpSession(); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + request.setSession(mockHttpSession); + + HttpSessionOAuthRememberMeServices oAuthRememberMeService = new HttpSessionOAuthRememberMeServices(); + + Map tokens = new HashMap(); + + { + OAuthConsumerToken token = new OAuthConsumerToken(); + token.setAccessToken(false); + tokens.put("resourceID1", token); + } + + { + OAuthConsumerToken token = new OAuthConsumerToken(); + token.setAccessToken(true); + tokens.put("resourceID2", token); + } + + oAuthRememberMeService.rememberTokens(tokens, request, response); + + Map storedTokens = oAuthRememberMeService.loadRememberedTokens(request, response); + + Assert.assertEquals(1, storedTokens.size()); + + Assert.assertNotNull(storedTokens.get("resourceID1")); + + } + +} diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/TestCoreOAuthProviderSupport.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/CoreOAuthProviderSupportTests.java similarity index 85% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/TestCoreOAuthProviderSupport.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/CoreOAuthProviderSupportTests.java index e5037efed..f64270759 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/TestCoreOAuthProviderSupport.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/CoreOAuthProviderSupportTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -37,7 +37,7 @@ * @author Ryan Heaton */ @RunWith(MockitoJUnitRunner.class) -public class TestCoreOAuthProviderSupport { +public class CoreOAuthProviderSupportTests { @Mock private HttpServletRequest request; @@ -48,7 +48,7 @@ public class TestCoreOAuthProviderSupport { public void testParseParameters() throws Exception { CoreOAuthProviderSupport support = new CoreOAuthProviderSupport(); when(request.getHeaders("Authorization")).thenReturn( - Collections.enumeration(Arrays.asList("OAuth realm=\"http://sp.example.com//",\n" + Collections.enumeration(Arrays.asList("OAuth realm=\"https://sp.example.com//",\n" + " oauth_consumer_key=\"0685bd9184jfhq22\",\n" + " oauth_token=\"ad180jjd733klru7\",\n" + " oauth_signature_method=\"HMAC-SHA1\",\n" @@ -57,7 +57,7 @@ public void testParseParameters() throws Exception { + " oauth_nonce=\"4572616e48616d6d65724c61686176\",\n" + " oauth_version=\"1.0\""))); Map params = support.parseParameters(request); - assertEquals("http://sp.example.com/", params.get("realm")); + assertEquals("https://sp.example.com/", params.get("realm")); assertEquals("0685bd9184jfhq22", params.get(OAuthConsumerParameter.oauth_consumer_key.toString())); assertEquals("ad180jjd733klru7", params.get(OAuthConsumerParameter.oauth_token.toString())); assertEquals("HMAC-SHA1", params.get(OAuthConsumerParameter.oauth_signature_method.toString())); @@ -82,7 +82,7 @@ public void testGetSignatureBaseString() throws Exception { } when(request.getHeaders("Authorization")).thenReturn( - Collections.enumeration(Arrays.asList("OAuth realm=\"http://sp.example.com//",\n" + Collections.enumeration(Arrays.asList("OAuth realm=\"https://sp.example.com//",\n" + " oauth_consumer_key=\"dpf43f3p2l4k3l03\",\n" + " oauth_token=\"nnch734d00sl2jdk\",\n" + " oauth_signature_method=\"HMAC-SHA1\",\n" @@ -93,12 +93,12 @@ public void testGetSignatureBaseString() throws Exception { when(request.getMethod()).thenReturn("gEt"); CoreOAuthProviderSupport support = new CoreOAuthProviderSupport(); - support.setBaseUrl("http://photos.example.net/"); + support.setBaseUrl("https://photos.example.net/"); when(request.getRequestURI()).thenReturn("photos"); String baseString = support.getSignatureBaseString(request); assertEquals( - "GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal", + "GET&https%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal", baseString); } diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestAccessTokenProcessingFilter.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/AccessTokenProcessingFilterTests.java similarity index 96% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestAccessTokenProcessingFilter.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/AccessTokenProcessingFilterTests.java index 415633680..5199c30fe 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestAccessTokenProcessingFilter.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/AccessTokenProcessingFilterTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -39,7 +39,7 @@ * @author Ryan Heaton */ @RunWith(MockitoJUnitRunner.class) -public class TestAccessTokenProcessingFilter { +public class AccessTokenProcessingFilterTests { @Mock private ConsumerDetails consumerDetails; @Mock diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestOAuthProcessingFilter.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/OAuthProcessingFilterTests.java similarity index 99% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestOAuthProcessingFilter.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/OAuthProcessingFilterTests.java index 117e21423..b164b14bb 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestOAuthProcessingFilter.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/OAuthProcessingFilterTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -65,7 +65,7 @@ * @author Ryan Heaton */ @RunWith(MockitoJUnitRunner.class) -public class TestOAuthProcessingFilter { +public class OAuthProcessingFilterTests { @Mock private OAuthProviderSupport providerSupport; @Mock diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestOAuthUserAuthorizationProcessingFilter.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/OAuthUserAuthorizationProcessingFilterTests.java similarity index 96% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestOAuthUserAuthorizationProcessingFilter.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/OAuthUserAuthorizationProcessingFilterTests.java index 068ce765b..ff967daed 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestOAuthUserAuthorizationProcessingFilter.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/OAuthUserAuthorizationProcessingFilterTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -36,7 +36,7 @@ /** * @author Ryan Heaton */ -public class TestOAuthUserAuthorizationProcessingFilter { +public class OAuthUserAuthorizationProcessingFilterTests { /** * tests the attempt to authenticate. diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestProtectedResourceProcessingFilter.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/ProtectedResourceProcessingFilterTests.java similarity index 96% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestProtectedResourceProcessingFilter.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/ProtectedResourceProcessingFilterTests.java index 9901a3c2a..8fc88f830 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestProtectedResourceProcessingFilter.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/ProtectedResourceProcessingFilterTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -38,7 +38,7 @@ /** * @author Ryan Heaton */ -public class TestProtectedResourceProcessingFilter { +public class ProtectedResourceProcessingFilterTests { /** * test onValidSignature diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestUnauthenticatedRequestTokenProcessingFilter.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/UnauthenticatedRequestTokenProcessingFilterTests.java similarity index 97% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestUnauthenticatedRequestTokenProcessingFilter.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/UnauthenticatedRequestTokenProcessingFilterTests.java index ae4ebf3e1..fb3fc1ac3 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestUnauthenticatedRequestTokenProcessingFilter.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/UnauthenticatedRequestTokenProcessingFilterTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -45,7 +45,7 @@ /** * @author Ryan Heaton */ -public class TestUnauthenticatedRequestTokenProcessingFilter { +public class UnauthenticatedRequestTokenProcessingFilterTests { /** * test onValidSignature diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestUserAuthorizationSuccessfulAuthenticationHandler.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/UserAuthorizationSuccessfulAuthenticationHandlerTests.java similarity index 85% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestUserAuthorizationSuccessfulAuthenticationHandler.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/UserAuthorizationSuccessfulAuthenticationHandlerTests.java index 8ab181152..adc112008 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/TestUserAuthorizationSuccessfulAuthenticationHandler.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/filter/UserAuthorizationSuccessfulAuthenticationHandlerTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -30,7 +30,7 @@ /** * @author Andrew McCall */ -public class TestUserAuthorizationSuccessfulAuthenticationHandler { +public class UserAuthorizationSuccessfulAuthenticationHandlerTests { /** * test determineTargetUrl @@ -45,7 +45,7 @@ public void testAuthenticationSuccess() throws Exception { handler.setRedirectStrategy(redirectStrategy); when(request.getAttribute(UserAuthorizationProcessingFilter.CALLBACK_ATTRIBUTE)).thenReturn( - "http://my.host.com/my/context"); + "https://my.host.com/my/context"); when(request.getAttribute(UserAuthorizationProcessingFilter.VERIFIER_ATTRIBUTE)).thenReturn("myver"); when(request.getParameter("requestToken")).thenReturn("mytok"); @@ -53,19 +53,19 @@ public void testAuthenticationSuccess() throws Exception { handler.onAuthenticationSuccess(request, response, null); verify(redirectStrategy).sendRedirect(request, response, - "http://my.host.com/my/context?oauth_token=mytok&oauth_verifier=myver"); + "https://my.host.com/my/context?oauth_token=mytok&oauth_verifier=myver"); handler = new UserAuthorizationSuccessfulAuthenticationHandler(); handler.setRedirectStrategy(redirectStrategy); when(request.getAttribute(UserAuthorizationProcessingFilter.CALLBACK_ATTRIBUTE)).thenReturn( - "http://my.hosting.com/my/context?with=some&query=parameter"); + "https://my.hosting.com/my/context?with=some&query=parameter"); when(request.getAttribute(UserAuthorizationProcessingFilter.VERIFIER_ATTRIBUTE)).thenReturn("myvera"); when(request.getParameter("requestToken")).thenReturn("mytoka"); handler.onAuthenticationSuccess(request, response, null); verify(redirectStrategy).sendRedirect(request, response, - "http://my.hosting.com/my/context?with=some&query=parameter&oauth_token=mytoka&oauth_verifier=myvera"); + "https://my.hosting.com/my/context?with=some&query=parameter&oauth_token=mytoka&oauth_verifier=myvera"); } } diff --git a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/nonce/TestInMemoryNonceServices.java b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/nonce/InMemoryNonceServicesTests.java similarity index 98% rename from spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/nonce/TestInMemoryNonceServices.java rename to spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/nonce/InMemoryNonceServicesTests.java index e9da160cd..a42f872ca 100644 --- a/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/nonce/TestInMemoryNonceServices.java +++ b/spring-security-oauth/src/test/java/org/springframework/security/oauth/provider/nonce/InMemoryNonceServicesTests.java @@ -14,7 +14,7 @@ * @author Ryan Heaton * @author Jilles van Gurp */ -public class TestInMemoryNonceServices { +public class InMemoryNonceServicesTests { private long now; private final InMemoryNonceServices nonceServices = new InMemoryNonceServices(); diff --git a/spring-security-oauth/src/test/resources/commons-logging.properties b/spring-security-oauth/src/test/resources/commons-logging.properties new file mode 100644 index 000000000..e60535218 --- /dev/null +++ b/spring-security-oauth/src/test/resources/commons-logging.properties @@ -0,0 +1,2 @@ +org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl +org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog \ No newline at end of file diff --git a/spring-security-oauth/src/test/resources/data.sql b/spring-security-oauth/src/test/resources/data.sql index e69de29bb..2ea0468f0 100644 --- a/spring-security-oauth/src/test/resources/data.sql +++ b/spring-security-oauth/src/test/resources/data.sql @@ -0,0 +1 @@ +select * from oauth_client_details; \ No newline at end of file diff --git a/spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/TestAuthorizationServerBeanDefinitionParser-context.xml b/spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/AuthorizationServerBeanDefinitionParserTests-context.xml similarity index 88% rename from spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/TestAuthorizationServerBeanDefinitionParser-context.xml rename to spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/AuthorizationServerBeanDefinitionParserTests-context.xml index a09c0710a..29aaff567 100644 --- a/spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/TestAuthorizationServerBeanDefinitionParser-context.xml +++ b/spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/AuthorizationServerBeanDefinitionParserTests-context.xml @@ -2,11 +2,11 @@ + xsi:schemaLocation="http://www.springframework.org/schema/beans%20https://www.springframework.org/schema/beans/spring-beans.xsd+%20%20%20%20%20%20%20%20%20%20%20%20%20%20http://www.springframework.org/schema/security%20https://www.springframework.org/schema/security/spring-security.xsd+%20%20%20%20%20%20%20%20%20%20%20%20%20%20http://www.springframework.org/schema/security/oauth%20https://www.springframework.org/schema/security/spring-security-oauth-1.0.xsd"> - + diff --git a/spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/TestConsumerServiceBeanDefinitionParser-context.xml b/spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/ConsumerServiceBeanDefinitionParserTests-context.xml similarity index 91% rename from spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/TestConsumerServiceBeanDefinitionParser-context.xml rename to spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/ConsumerServiceBeanDefinitionParserTests-context.xml index 92249091d..4e1bf82d7 100644 --- a/spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/TestConsumerServiceBeanDefinitionParser-context.xml +++ b/spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/ConsumerServiceBeanDefinitionParserTests-context.xml @@ -2,11 +2,11 @@ + xsi:schemaLocation="http://www.springframework.org/schema/beans%20https://www.springframework.org/schema/beans/spring-beans.xsd+%20%20%20%20%20%20%20%20%20%20%20%20%20%20http://www.springframework.org/schema/security%20https://www.springframework.org/schema/security/spring-security.xsd+%20%20%20%20%20%20%20%20%20%20%20%20%20%20http://www.springframework.org/schema/security/oauth%20https://www.springframework.org/schema/security/spring-security-oauth-1.0.xsd"> - + diff --git a/spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/TestFilterChainInitialization-context.xml b/spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/FilterChainInitializationTests-context.xml similarity index 76% rename from spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/TestFilterChainInitialization-context.xml rename to spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/FilterChainInitializationTests-context.xml index 43c638374..cfb0a9f00 100644 --- a/spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/TestFilterChainInitialization-context.xml +++ b/spring-security-oauth/src/test/resources/org/springframework/security/oauth/config/FilterChainInitializationTests-context.xml @@ -2,15 +2,15 @@ + https://www.springframework.org/schema/security/spring-security-oauth-1.0.xsd"> - + diff --git a/spring-security-oauth/src/test/resources/simplelog.properties b/spring-security-oauth/src/test/resources/simplelog.properties new file mode 100644 index 000000000..cba65bf4c --- /dev/null +++ b/spring-security-oauth/src/test/resources/simplelog.properties @@ -0,0 +1,2 @@ +org.apache.commons.logging.simplelog.defaultlog=warn +#org.apache.commons.logging.simplelog.log.org.springframework.security=debug diff --git a/spring-security-oauth/template.mf b/spring-security-oauth/template.mf deleted file mode 100644 index 7f0fbb454..000000000 --- a/spring-security-oauth/template.mf +++ /dev/null @@ -1,28 +0,0 @@ -Bundle-SymbolicName: org.springframework.security.oauth -Bundle-Name: Spring Security OAuth -Bundle-Vendor: SpringSource -Bundle-ManifestVersion: 2 -Import-Template: - org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", - org.apache.commons.codec.*;version="[1.3, 2.0.0)", - org.springframework.beans.*;version="${spring.osgi.range}", - org.springframework.core.*;version="${spring.osgi.range}", - org.springframework.expression.*;version="${spring.osgi.range}", - org.springframework.jdbc.*;version="${spring.osgi.range}", - org.springframework.web.*;version="${spring.osgi.range}", - org.springframework.dao.*;version="${spring.osgi.range}", - org.springframework.http.*;version="${spring.osgi.range}", - org.springframework.context.*;version="${spring.osgi.range}", - org.springframework.util.*;version="${spring.osgi.range}", - org.springframework.security.*;version="${security.osgi.range}", - org.aopalliance.*;version="0", - org.w3c.dom.*;version="0", - javax.servlet.*;version="[2.3,4.0)", - javax.crypto.*;version="0", - javax.sql.*;version="0", - javax.xml.*;version="0" -Ignored-Existing-Headers: - Bnd-LastModified, - Import-Package, - Export-Package, - Tool diff --git a/spring-security-oauth2/.springBeans b/spring-security-oauth2/.springBeans deleted file mode 100644 index d4fb00bd2..000000000 --- a/spring-security-oauth2/.springBeans +++ /dev/null @@ -1,18 +0,0 @@ - - - 1 - - - - - - - src/test/resources/org/springframework/security/oauth2/config/TestClientDetailsServiceBeanDefinitionParser-context.xml - src/test/resources/org/springframework/security/oauth2/config/TestResourceServerBeanDefinitionParser-context.xml - src/test/resources/org/springframework/security/oauth2/config/TestResourceBeanDefinitionParser-context.xml - src/test/resources/org/springframework/security/oauth2/config/authorization-server-vanilla.xml - src/test/resources/org/springframework/security/oauth2/config/authorization-server-custom-grant.xml - - - - diff --git a/spring-security-oauth2/pom.xml b/spring-security-oauth2/pom.xml index ceda1ae6a..c3bc56706 100644 --- a/spring-security-oauth2/pom.xml +++ b/spring-security-oauth2/pom.xml @@ -1,21 +1,34 @@ - + 4.0.0 org.springframework.security.oauth spring-security-oauth-parent - 1.0.1.BUILD-SNAPSHOT + 2.5.3.BUILD-SNAPSHOT spring-security-oauth2 OAuth2 for Spring Security Module for providing OAuth2 support to Spring Security - - 1.9.2 - 2.1.1 - + + 2.10.5.1 + 3.0.1 + 1.1.1.RELEASE + 1.7.4 + + + + + spring5 + + 2.10.5.1 + 3.1.0 + 1.6.1 + + + @@ -34,7 +47,7 @@ - + @@ -50,86 +63,79 @@ - - com.springsource.bundlor - com.springsource.bundlor.maven - javax.servlet - servlet-api - 2.5 - provided + javax.servlet-api + + ${servlet-api.version} + true org.springframework spring-beans - ${spring.version} org.springframework spring-core - ${spring.version} org.springframework spring-context - ${spring.version} org.springframework spring-aop - ${spring.version} true org.springframework spring-jdbc - ${spring.version} true org.springframework spring-webmvc - ${spring.version} org.springframework spring-test - ${spring.version} test org.springframework.security spring-security-core - ${spring.security.version} org.springframework.security spring-security-config - ${spring.security.version} + + + + org.springframework.security + spring-security-jwt + true org.springframework.security spring-security-web - ${spring.security.version} org.springframework @@ -141,19 +147,26 @@ commons-codec commons-codec - 1.3 - org.codehaus.jackson - jackson-mapper-asl - ${jackson1.version} + org.springframework.data + spring-data-redis + ${spring.data.redis.version} + true + + + + redis.clients + jedis + ${redis.clients.version} + true com.fasterxml.jackson.core jackson-annotations - ${jackson2.version} + 2.10.5 true @@ -164,10 +177,24 @@ true + + org.apache.httpcomponents + httpclient + 4.5.13 + true + + + + com.squareup.okhttp3 + mockwebserver + 3.7.0 + test + + junit junit - 4.8.2 + ${junit.version} compile true @@ -175,28 +202,28 @@ org.powermock powermock-module-junit4 - 1.4.10 + ${powermock.version} test org.powermock powermock-api-mockito - 1.4.10 + ${powermock.version} test org.mockito mockito-core - 1.9.0 + ${mockito.version} test org.slf4j slf4j-api - 1.5.8 + 1.7.6 test @@ -206,6 +233,7 @@ 2.0.0 test + diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/DefaultOAuth2ClientContext.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/DefaultOAuth2ClientContext.java index dc4657c2e..18b737279 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/DefaultOAuth2ClientContext.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/DefaultOAuth2ClientContext.java @@ -1,8 +1,8 @@ package org.springframework.security.oauth2.client; import java.io.Serializable; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.security.oauth2.client.token.AccessTokenRequest; import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; @@ -10,9 +10,13 @@ /** * The OAuth 2 security context (for a specific user or client or combination thereof). - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer */ +@Deprecated public class DefaultOAuth2ClientContext implements OAuth2ClientContext, Serializable { private static final long serialVersionUID = 914967629530462926L; @@ -21,7 +25,7 @@ public class DefaultOAuth2ClientContext implements OAuth2ClientContext, Serializ private AccessTokenRequest accessTokenRequest; - private Map state = new HashMap(); + private Map state = new ConcurrentHashMap(); public DefaultOAuth2ClientContext() { this(new DefaultAccessTokenRequest()); @@ -50,6 +54,7 @@ public AccessTokenRequest getAccessTokenRequest() { } public void setPreservedState(String stateKey, Object preservedState) { + state.clear(); state.put(stateKey, preservedState); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/DefaultOAuth2RequestAuthenticator.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/DefaultOAuth2RequestAuthenticator.java new file mode 100644 index 000000000..c6b8d5a26 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/DefaultOAuth2RequestAuthenticator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2013-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.client; + +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.security.oauth2.client.http.AccessTokenRequiredException; +import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.util.StringUtils; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class DefaultOAuth2RequestAuthenticator implements OAuth2RequestAuthenticator { + + @Override + public void authenticate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext clientContext, + ClientHttpRequest request) { + OAuth2AccessToken accessToken = clientContext.getAccessToken(); + if (accessToken == null) { + throw new AccessTokenRequiredException(resource); + } + String tokenType = accessToken.getTokenType(); + if (!StringUtils.hasText(tokenType)) { + tokenType = OAuth2AccessToken.BEARER_TYPE; // we'll assume basic bearer token type if none is specified. + } else if (tokenType.equalsIgnoreCase(OAuth2AccessToken.BEARER_TYPE)) { + // gh-1346 + tokenType = OAuth2AccessToken.BEARER_TYPE; // Ensure we use the correct syntax for the "Bearer" authentication scheme + } + request.getHeaders().set("Authorization", String.format("%s %s", tokenType, accessToken.getValue())); + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2ClientContext.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2ClientContext.java index de2b27876..dc02aaf3a 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2ClientContext.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2ClientContext.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -16,9 +16,13 @@ import org.springframework.security.oauth2.common.OAuth2AccessToken; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface OAuth2ClientContext { /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RequestAuthenticator.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RequestAuthenticator.java new file mode 100644 index 000000000..4cbed416d --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RequestAuthenticator.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2014 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.client; + +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public interface OAuth2RequestAuthenticator { + + void authenticate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext clientContext, ClientHttpRequest request); + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RestOperations.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RestOperations.java index 4e88baf19..839f5fd77 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RestOperations.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RestOperations.java @@ -1,30 +1,40 @@ /* - * Cloud Foundry 2012.02.03 Beta - * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. + * Copyright 2002-2012 the original author or authors. * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. + * 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.client; +import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.web.client.RestOperations; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface OAuth2RestOperations extends RestOperations { OAuth2AccessToken getAccessToken() throws UserRedirectRequiredException; OAuth2ClientContext getOAuth2ClientContext(); + + OAuth2ProtectedResourceDetails getResource(); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RestTemplate.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RestTemplate.java index b37c0fb61..3f8c18452 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RestTemplate.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/OAuth2RestTemplate.java @@ -2,10 +2,12 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.util.Arrays; +import java.util.Calendar; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; @@ -24,7 +26,8 @@ import org.springframework.security.oauth2.common.AuthenticationScheme; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; -import org.springframework.util.StringUtils; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; import org.springframework.web.client.RequestCallback; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.ResponseExtractor; @@ -33,10 +36,14 @@ /** * Rest template that is able to make OAuth2-authenticated REST requests with the credentials of the provided resource. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class OAuth2RestTemplate extends RestTemplate implements OAuth2RestOperations { private final OAuth2ProtectedResourceDetails resource; @@ -49,6 +56,10 @@ public class OAuth2RestTemplate extends RestTemplate implements OAuth2RestOperat private boolean retryBadAccessTokens = true; + private OAuth2RequestAuthenticator authenticator = new DefaultOAuth2RequestAuthenticator(); + + private int clockSkew = 30; + public OAuth2RestTemplate(OAuth2ProtectedResourceDetails resource) { this(resource, new DefaultOAuth2ClientContext()); } @@ -64,6 +75,16 @@ public OAuth2RestTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientC setErrorHandler(new OAuth2ErrorHandler(resource)); } + /** + * Strategy for extracting an Authorization header from an access token and the request details. Defaults to the + * simple form "TOKEN_TYPE TOKEN_VALUE". + * + * @param authenticator the authenticator to use + */ + public void setAuthenticator(OAuth2RequestAuthenticator authenticator) { + this.authenticator = authenticator; + } + /** * Flag to determine whether a request that has an existing access token, and which then leads to an * AccessTokenRequiredException should be retried (immediately, once). Useful if the remote server doesn't recognize @@ -82,35 +103,29 @@ public void setErrorHandler(ResponseErrorHandler errorHandler) { } super.setErrorHandler(errorHandler); } + + @Override + public OAuth2ProtectedResourceDetails getResource() { + return resource; + } @Override protected ClientHttpRequest createRequest(URI uri, HttpMethod method) throws IOException { OAuth2AccessToken accessToken = getAccessToken(); - String tokenType = accessToken.getTokenType(); - if (!StringUtils.hasText(tokenType)) { - tokenType = OAuth2AccessToken.BEARER_TYPE; // we'll assume basic bearer token type if none is specified. + AuthenticationScheme authenticationScheme = resource.getAuthenticationScheme(); + if (AuthenticationScheme.query.equals(authenticationScheme) + || AuthenticationScheme.form.equals(authenticationScheme)) { + uri = appendQueryParameter(uri, accessToken); } - if (OAuth2AccessToken.BEARER_TYPE.equalsIgnoreCase(tokenType) - || OAuth2AccessToken.OAUTH2_TYPE.equalsIgnoreCase(tokenType)) { - AuthenticationScheme bearerTokenMethod = resource.getAuthenticationScheme(); - if (AuthenticationScheme.query.equals(bearerTokenMethod) - || AuthenticationScheme.form.equals(bearerTokenMethod)) { - uri = appendQueryParameter(uri, accessToken); - } - ClientHttpRequest req = super.createRequest(uri, method); + ClientHttpRequest req = super.createRequest(uri, method); - if (AuthenticationScheme.header.equals(bearerTokenMethod)) { - req.getHeaders().add("Authorization", - String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, accessToken.getValue())); - } - return req; - } - else { - throw new OAuth2AccessDeniedException("Unsupported access token type: " + tokenType); + if (AuthenticationScheme.header.equals(authenticationScheme)) { + authenticator.authenticate(resource, getOAuth2ClientContext(), req); } + return req; } @@ -130,7 +145,7 @@ protected T doExecute(URI url, HttpMethod method, RequestCallback requestCal } catch (InvalidTokenException e) { // Don't reveal the token value in case it is logged - rethrow = new OAuth2AccessDeniedException("Invalid token for client="+getClientId()); + rethrow = new OAuth2AccessDeniedException("Invalid token for client=" + getClientId()); } if (accessToken != null && retryBadAccessTokens) { context.setAccessToken(null); @@ -139,7 +154,7 @@ protected T doExecute(URI url, HttpMethod method, RequestCallback requestCal } catch (InvalidTokenException e) { // Don't reveal the token value in case it is logged - rethrow = new OAuth2AccessDeniedException("Invalid token for client="+getClientId()); + rethrow = new OAuth2AccessDeniedException("Invalid token for client=" + getClientId()); } } throw rethrow; @@ -163,7 +178,7 @@ public OAuth2AccessToken getAccessToken() throws UserRedirectRequiredException { OAuth2AccessToken accessToken = context.getAccessToken(); - if (accessToken == null || accessToken.isExpired()) { + if (accessToken == null || hasTokenExpired(accessToken)) { try { accessToken = acquireAccessToken(context); } @@ -174,7 +189,7 @@ public OAuth2AccessToken getAccessToken() throws UserRedirectRequiredException { if (stateKey != null) { Object stateToPreserve = e.getStateToPreserve(); if (stateToPreserve == null) { - stateToPreserve = "state"; + stateToPreserve = "NONE"; } context.setPreservedState(stateKey, stateToPreserve); } @@ -184,6 +199,16 @@ public OAuth2AccessToken getAccessToken() throws UserRedirectRequiredException { return accessToken; } + private boolean hasTokenExpired(OAuth2AccessToken accessToken) { + Calendar now = Calendar.getInstance(); + Calendar expiresAt = (Calendar) now.clone(); + if (accessToken.getExpiration() != null) { + expiresAt.setTime(accessToken.getExpiration()); + expiresAt.add(Calendar.SECOND, -this.clockSkew); + } + return now.after(expiresAt); + } + /** * @return the context for this template */ @@ -264,6 +289,42 @@ protected URI appendQueryParameter(URI uri, OAuth2AccessToken accessToken) { public void setAccessTokenProvider(AccessTokenProvider accessTokenProvider) { this.accessTokenProvider = accessTokenProvider; + propagateClockSkewToAccessTokenProvider(this.clockSkew, accessTokenProvider); + } + + /** + * Sets the maximum acceptable clock skew, which is used when checking the + * {@link OAuth2AccessToken access token} expiry. The default is 30 seconds. + * + * @param clockSkew the maximum acceptable clock skew + */ + public void setClockSkew(int clockSkew) { + Assert.isTrue(clockSkew >= 0, "clockSkew must be >= 0"); + this.clockSkew = clockSkew; + propagateClockSkewToAccessTokenProvider(clockSkew, this.accessTokenProvider); } -} + /** + * Propagates the maximum acceptable clock skew, which is used when checking the + * {@link OAuth2AccessToken access token} expiry into the given {@link AccessTokenProvider} if it is an instance of + * {@link AccessTokenProviderChain}. + *

+ * Note: The clock skew value is injected via reflection as version 2.5.0 was the final minor release before EOL of + * this project and the public API must not be changed in patch releases. + * + * @param clockSkew the maximum acceptable clock skew + * @param accessTokenProvider the access token provider + */ + private static void propagateClockSkewToAccessTokenProvider(int clockSkew, AccessTokenProvider accessTokenProvider) { + if (!(accessTokenProvider instanceof AccessTokenProviderChain)) { + return; + } + + Field field = ReflectionUtils.findField(accessTokenProvider.getClass(), "clockSkew"); + if (field == null) { + return; + } + field.setAccessible(true); + ReflectionUtils.setField(field, accessTokenProvider, clockSkew); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/discovery/ProviderConfiguration.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/discovery/ProviderConfiguration.java new file mode 100644 index 000000000..43caf5989 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/discovery/ProviderConfiguration.java @@ -0,0 +1,98 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.client.discovery; + +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Configuration information for an OAuth 2.0 Provider. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Joe Grandja + * @since 2.2 + * @see ProviderDiscoveryClient + * @see OpenID Connect Discovery 1.0 + */ +@Deprecated +public class ProviderConfiguration { + private URL issuer; + private URL authorizationEndpoint; + private URL tokenEndpoint; + private URL userInfoEndpoint; + private URL jwkSetUri; + + public URL getIssuer() { + return this.issuer; + } + + public URL getAuthorizationEndpoint() { + return this.authorizationEndpoint; + } + + public URL getTokenEndpoint() { + return this.tokenEndpoint; + } + + public URL getUserInfoEndpoint() { + return this.userInfoEndpoint; + } + + public URL getJwkSetUri() { + return this.jwkSetUri; + } + + public static class Builder { + private ProviderConfiguration providerConfiguration = new ProviderConfiguration(); + + public Builder() { + } + + public void issuer(String isssuer) { + this.providerConfiguration.issuer = this.toURL(isssuer); + } + + public void authorizationEndpoint(String authorizationEndpoint) { + this.providerConfiguration.authorizationEndpoint = this.toURL(authorizationEndpoint); + } + + public void tokenEndpoint(String tokenEndpoint) { + this.providerConfiguration.tokenEndpoint = this.toURL(tokenEndpoint); + } + + public void userInfoEndpoint(String userInfoEndpoint) { + this.providerConfiguration.userInfoEndpoint = this.toURL(userInfoEndpoint); + } + + public void jwkSetUri(String jwkSetUri) { + this.providerConfiguration.jwkSetUri = this.toURL(jwkSetUri); + } + + public ProviderConfiguration build() { + return this.providerConfiguration; + } + + private URL toURL(String urlStr) { + try { + return new URL(urlStr); + } catch (MalformedURLException ex) { + throw new IllegalArgumentException("Unable to convert '" + urlStr + "' to URL: " + ex.getMessage(), ex); + } + } + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/discovery/ProviderDiscoveryClient.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/discovery/ProviderDiscoveryClient.java new file mode 100644 index 000000000..d33fa7c97 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/discovery/ProviderDiscoveryClient.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.client.discovery; + +import org.springframework.util.Assert; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; +import java.util.Map; + +/** + * A client that is able to discover provider configuration information + * as defined by the OpenID Connect Discovery 1.0 specification. + * + *

+ * NOTE: This is a partial implementation that only discovers a small subset + * of the available provider configuration information. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Joe Grandja + * @since 2.2 + * @see ProviderConfiguration + * @see OpenID Connect Discovery 1.0 + */ +@Deprecated +public class ProviderDiscoveryClient { + private static final String PROVIDER_END_PATH = "/.well-known/openid-configuration"; + private static final String ISSUER_ATTR_NAME = "issuer"; + private static final String AUTHORIZATION_ENDPOINT_ATTR_NAME = "authorization_endpoint"; + private static final String TOKEN_ENDPOINT_ATTR_NAME = "token_endpoint"; + private static final String USERINFO_ENDPOINT_ATTR_NAME = "userinfo_endpoint"; + private static final String JWK_SET_URI_ATTR_NAME = "jwks_uri"; + private final RestTemplate restTemplate = new RestTemplate(); + private final URI providerLocation; + + public ProviderDiscoveryClient(String providerLocationUri) { + Assert.hasText(providerLocationUri, "providerLocationUri cannot be empty"); + try { + this.providerLocation = UriComponentsBuilder.fromHttpUrl(providerLocationUri) + .path(PROVIDER_END_PATH) + .build() + .encode() + .toUri(); + } catch (Exception ex) { + throw new IllegalArgumentException("Invalid URI for providerLocationUri: " + ex.getMessage(), ex); + } + } + + /** + * Discover the provider configuration information. + * + * @throws RestClientException if the provider does not support discovery or for any HTTP-related errors + * @return the provider configuration information + */ + public ProviderConfiguration discover() { + Map responseAttributes = this.restTemplate.getForObject(this.providerLocation, Map.class); + + ProviderConfiguration.Builder builder = new ProviderConfiguration.Builder(); + + builder.issuer((String)responseAttributes.get(ISSUER_ATTR_NAME)); + builder.authorizationEndpoint((String)responseAttributes.get(AUTHORIZATION_ENDPOINT_ATTR_NAME)); + if (responseAttributes.containsKey(TOKEN_ENDPOINT_ATTR_NAME)) { + builder.tokenEndpoint((String)responseAttributes.get(TOKEN_ENDPOINT_ATTR_NAME)); + } + if (responseAttributes.containsKey(USERINFO_ENDPOINT_ATTR_NAME)) { + builder.userInfoEndpoint((String)responseAttributes.get(USERINFO_ENDPOINT_ATTR_NAME)); + } + if (responseAttributes.containsKey(JWK_SET_URI_ATTR_NAME)) { + builder.jwkSetUri((String)responseAttributes.get(JWK_SET_URI_ATTR_NAME)); + } + + return builder.build(); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/OAuth2AuthenticationFailureEvent.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/OAuth2AuthenticationFailureEvent.java new file mode 100644 index 000000000..a32ccd1cc --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/OAuth2AuthenticationFailureEvent.java @@ -0,0 +1,39 @@ +package org.springframework.security.oauth2.client.filter; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent; +import org.springframework.security.core.AuthenticationException; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + */ +@SuppressWarnings("serial") +@Deprecated +public class OAuth2AuthenticationFailureEvent extends AbstractAuthenticationFailureEvent { + + public OAuth2AuthenticationFailureEvent(AuthenticationException exception) { + super(new FailedOAuthClientAuthentication(), exception); + } + +} + +@SuppressWarnings("serial") +class FailedOAuthClientAuthentication extends AbstractAuthenticationToken { + + public FailedOAuthClientAuthentication() { + super(null); + } + + @Override + public Object getCredentials() { + return ""; + } + + @Override + public Object getPrincipal() { + return "UNKNOWN"; + } + +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/OAuth2ClientAuthenticationProcessingFilter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/OAuth2ClientAuthenticationProcessingFilter.java index 1e7edf497..39e693bb0 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/OAuth2ClientAuthenticationProcessingFilter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/OAuth2ClientAuthenticationProcessingFilter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -18,19 +18,27 @@ import java.io.IOException; +import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.security.authentication.AuthenticationDetailsSource; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.event.AuthenticationSuccessEvent; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.client.OAuth2RestOperations; import org.springframework.security.oauth2.client.http.AccessTokenRequiredException; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager; +import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails; +import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetailsSource; import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.util.Assert; @@ -38,16 +46,24 @@ /** * An OAuth2 client filter that can be used to acquire an OAuth2 access token from an authorization server, and load an * authentication object into the SecurityContext - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Vidya Valmikinathan * */ +@Deprecated public class OAuth2ClientAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { public OAuth2RestOperations restTemplate; private ResourceServerTokenServices tokenServices; + private AuthenticationDetailsSource authenticationDetailsSource = new OAuth2AuthenticationDetailsSource(); + + private ApplicationEventPublisher eventPublisher; + /** * Reference to a CheckTokenServices that can validate an OAuth2AccessToken * @@ -65,10 +81,17 @@ public void setTokenServices(ResourceServerTokenServices tokenServices) { public void setRestTemplate(OAuth2RestOperations restTemplate) { this.restTemplate = restTemplate; } - + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + super.setApplicationEventPublisher(eventPublisher); + } + public OAuth2ClientAuthenticationProcessingFilter(String defaultFilterProcessesUrl) { super(defaultFilterProcessesUrl); - setAuthenticationManager(new OAuth2AuthenticationManager()); + setAuthenticationManager(new NoopAuthenticationManager()); + setAuthenticationDetailsSource(authenticationDetailsSource); } @Override @@ -81,17 +104,46 @@ public void afterPropertiesSet() { public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { - OAuth2AccessToken accessToken = restTemplate.getAccessToken(); + OAuth2AccessToken accessToken; + try { + accessToken = restTemplate.getAccessToken(); + } catch (OAuth2Exception e) { + BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e); + publish(new OAuth2AuthenticationFailureEvent(bad)); + throw bad; + } try { OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue()); + if (authenticationDetailsSource!=null) { + request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue()); + request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType()); + result.setDetails(authenticationDetailsSource.buildDetails(request)); + } + publish(new AuthenticationSuccessEvent(result)); return result; } catch (InvalidTokenException e) { - throw new BadCredentialsException("Could not obtain user details from token", e); + BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e); + publish(new OAuth2AuthenticationFailureEvent(bad)); + throw bad; } } + private void publish(ApplicationEvent event) { + if (eventPublisher!=null) { + eventPublisher.publishEvent(event); + } + } + + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, + FilterChain chain, Authentication authResult) throws IOException, ServletException { + super.successfulAuthentication(request, response, chain, authResult); + // Nearly a no-op, but if there is a ClientTokenServices then the token will now be stored + restTemplate.getAccessToken(); + } + @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { @@ -104,5 +156,15 @@ protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServle super.unsuccessfulAuthentication(request, response, failed); } } + + private static class NoopAuthenticationManager implements AuthenticationManager { + + @Override + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + throw new UnsupportedOperationException("No authentication should be done with this AuthenticationManager"); + } + + } } \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/OAuth2ClientContextFilter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/OAuth2ClientContextFilter.java index 433f1a3d6..42ce00370 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/OAuth2ClientContextFilter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/OAuth2ClientContextFilter.java @@ -2,8 +2,6 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.Enumeration; import java.util.Map; import javax.servlet.Filter; @@ -19,39 +17,44 @@ import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; import org.springframework.security.oauth2.common.DefaultThrowableAnalyzer; import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.PortResolver; -import org.springframework.security.web.PortResolverImpl; import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.util.ThrowableAnalyzer; -import org.springframework.security.web.util.UrlUtils; import org.springframework.util.Assert; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.web.util.NestedServletException; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; /** * Security filter for an OAuth2 client. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class OAuth2ClientContextFilter implements Filter, InitializingBean { /** - * Key in request attributes for the current URI in case it is needed by rest client code that needs to send a - * redirect URI to an authorization server. + * Key in request attributes for the current URI in case it is needed by + * rest client code that needs to send a redirect URI to an authorization + * server. */ public static final String CURRENT_URI = "currentUri"; - private PortResolver portResolver = new PortResolverImpl(); - private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer(); private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); public void afterPropertiesSet() throws Exception { - Assert.notNull(redirectStrategy, "A redirect strategy must be supplied."); + Assert.notNull(redirectStrategy, + "A redirect strategy must be supplied."); } - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) + public void doFilter(ServletRequest servletRequest, + ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; @@ -59,19 +62,17 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo try { chain.doFilter(servletRequest, servletResponse); - } - catch (IOException ex) { + } catch (IOException ex) { throw ex; - } - catch (Exception ex) { + } catch (Exception ex) { // Try to extract a SpringSecurityException from the stacktrace Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex); UserRedirectRequiredException redirect = (UserRedirectRequiredException) throwableAnalyzer - .getFirstThrowableOfType(UserRedirectRequiredException.class, causeChain); + .getFirstThrowableOfType( + UserRedirectRequiredException.class, causeChain); if (redirect != null) { redirectUser(redirect, request, response); - } - else { + } else { if (ex instanceof ServletException) { throw (ServletException) ex; } @@ -86,74 +87,64 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo /** * Redirect the user according to the specified exception. * - * @param resourceThatNeedsAuthorization - * @param e The user redirect exception. - * @param request The request. - * @param response The response. + * @param e + * The user redirect exception. + * @param request + * The request. + * @param response + * The response. */ - protected void redirectUser(UserRedirectRequiredException e, HttpServletRequest request, - HttpServletResponse response) throws IOException { + protected void redirectUser(UserRedirectRequiredException e, + HttpServletRequest request, HttpServletResponse response) + throws IOException { String redirectUri = e.getRedirectUri(); - StringBuilder builder = new StringBuilder(redirectUri); + UriComponentsBuilder builder = UriComponentsBuilder + .fromHttpUrl(redirectUri); Map requestParams = e.getRequestParams(); - char appendChar = redirectUri.indexOf('?') < 0 ? '?' : '&'; for (Map.Entry param : requestParams.entrySet()) { - try { - builder.append(appendChar).append(param.getKey()).append('=') - .append(URLEncoder.encode(param.getValue(), "UTF-8")); - } - catch (UnsupportedEncodingException uee) { - throw new IllegalStateException(uee); - } - appendChar = '&'; + builder.queryParam(param.getKey(), param.getValue()); } if (e.getStateKey() != null) { - builder.append(appendChar).append("state").append('=').append(e.getStateKey()); + builder.queryParam("state", e.getStateKey()); } - this.redirectStrategy.sendRedirect(request, response, builder.toString()); - + this.redirectStrategy.sendRedirect(request, response, builder.build() + .encode().toUriString()); } /** * Calculate the current URI given the request. * - * @param request The request. + * @param request + * The request. * @return The current uri. */ - protected String calculateCurrentUri(HttpServletRequest request) throws UnsupportedEncodingException { - StringBuilder queryBuilder = new StringBuilder(); - @SuppressWarnings("unchecked") - Enumeration paramNames = request.getParameterNames(); - while (paramNames.hasMoreElements()) { - String name = (String) paramNames.nextElement(); - if (!"code".equals(name)) { - String[] parameterValues = request.getParameterValues(name); - if (parameterValues.length == 0) { - queryBuilder.append(URLEncoder.encode(name, "UTF-8")); - } - else { - for (int i = 0; i < parameterValues.length; i++) { - String parameterValue = parameterValues[i]; - queryBuilder.append(URLEncoder.encode(name, "UTF-8")).append('=') - .append(URLEncoder.encode(parameterValue, "UTF-8")); - if (i + 1 < parameterValues.length) { - queryBuilder.append('&'); - } - } - } - } - - if (paramNames.hasMoreElements() && queryBuilder.length() > 0) { - queryBuilder.append('&'); - } + protected String calculateCurrentUri(HttpServletRequest request) + throws UnsupportedEncodingException { + ServletUriComponentsBuilder builder = ServletUriComponentsBuilder + .fromRequest(request); + // Now work around SPR-10172... + String queryString = request.getQueryString(); + boolean legalSpaces = queryString != null && queryString.contains("+"); + if (legalSpaces) { + builder.replaceQuery(queryString.replace("+", "%20")); } - - return UrlUtils.buildFullRequestUrl(request.getScheme(), request.getServerName(), - portResolver.getServerPort(request), request.getRequestURI(), - queryBuilder.length() > 0 ? queryBuilder.toString() : null); + UriComponents uri = null; + try { + uri = builder.replaceQueryParam("code").build(true); + } catch (IllegalArgumentException ex) { + // ignore failures to parse the url (including query string). does't + // make sense for redirection purposes anyway. + return null; + } + String query = uri.getQuery(); + if (legalSpaces) { + query = query.replace("%20", "+"); + } + return ServletUriComponentsBuilder.fromUri(uri.toUri()) + .replaceQuery(query).build().toString(); } public void init(FilterConfig filterConfig) throws ServletException { @@ -166,10 +157,6 @@ public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) { this.throwableAnalyzer = throwableAnalyzer; } - public void setPortResolver(PortResolver portResolver) { - this.portResolver = portResolver; - } - public void setRedirectStrategy(RedirectStrategy redirectStrategy) { this.redirectStrategy = redirectStrategy; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/state/DefaultStateKeyGenerator.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/state/DefaultStateKeyGenerator.java index 569007200..7bf3bb131 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/state/DefaultStateKeyGenerator.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/state/DefaultStateKeyGenerator.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -16,9 +16,13 @@ import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class DefaultStateKeyGenerator implements StateKeyGenerator { private RandomValueStringGenerator generator = new RandomValueStringGenerator(); diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/state/StateKeyGenerator.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/state/StateKeyGenerator.java index 58fc0638c..bb904f7e0 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/state/StateKeyGenerator.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/filter/state/StateKeyGenerator.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -17,10 +17,14 @@ /** * Stategy for generating random keys for state. The state key is important protection for client apps against * cross-site request forgery. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface StateKeyGenerator { /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/AccessTokenRequiredException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/AccessTokenRequiredException.java index e60df7b02..e8509a6f9 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/AccessTokenRequiredException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/AccessTokenRequiredException.java @@ -4,8 +4,13 @@ import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class AccessTokenRequiredException extends InsufficientAuthenticationException { private final OAuth2ProtectedResourceDetails resource; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ErrorHandler.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ErrorHandler.java index 33a9e746b..cfb495cfd 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ErrorHandler.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ErrorHandler.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -15,26 +15,35 @@ */ package org.springframework.security.oauth2.client.http; -import java.io.IOException; -import java.util.List; -import java.util.Map; - +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; -import org.springframework.web.client.DefaultResponseErrorHandler; -import org.springframework.web.client.HttpMessageConverterExtractor; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; +import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.client.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; /** * Error handler specifically for an oauth 2 response. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@Deprecated public class OAuth2ErrorHandler implements ResponseErrorHandler { private final ResponseErrorHandler errorHandler; @@ -47,7 +56,8 @@ public class OAuth2ErrorHandler implements ResponseErrorHandler { * Construct an error handler that can deal with OAuth2 concerns before handling the error in the default fashion. */ public OAuth2ErrorHandler(OAuth2ProtectedResourceDetails resource) { - this(new DefaultResponseErrorHandler(), resource); + this.resource = resource; + this.errorHandler = new DefaultResponseErrorHandler(); } /** @@ -68,36 +78,105 @@ public OAuth2ErrorHandler(ResponseErrorHandler errorHandler, OAuth2ProtectedReso } public boolean hasError(ClientHttpResponse response) throws IOException { - return this.errorHandler.hasError(response); + return HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series()) + || this.errorHandler.hasError(response); } - public void handleError(ClientHttpResponse response) throws IOException { - - HttpMessageConverterExtractor extractor = new HttpMessageConverterExtractor( - OAuth2Exception.class, messageConverters); - try { - OAuth2Exception body = extractor.extractData(response); - if (body != null) { - // If we can get an OAuth2Exception already from the body, it is likely to have more information than - // the header does, so just re-throw it here. - throw body; - } + public void handleError(final ClientHttpResponse response) throws IOException { + if (!HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series())) { + // We should only care about 400 level errors. Ex: A 500 server error shouldn't + // be an oauth related error. + errorHandler.handleError(response); } - catch (RestClientException e) { - // ignore - } - - // first try: www-authenticate error - List authenticateHeaders = response.getHeaders().get("WWW-Authenticate"); - if (authenticateHeaders != null) { - for (String authenticateHeader : authenticateHeaders) { - maybeThrowExceptionFromHeader(authenticateHeader, OAuth2AccessToken.BEARER_TYPE); - maybeThrowExceptionFromHeader(authenticateHeader, OAuth2AccessToken.OAUTH2_TYPE); + else { + // Need to use buffered response because input stream may need to be consumed multiple times. + ClientHttpResponse bufferedResponse = new ClientHttpResponse() { + private byte[] lazyBody; + + public HttpStatus getStatusCode() throws IOException { + return response.getStatusCode(); + } + + public synchronized InputStream getBody() throws IOException { + if (lazyBody == null) { + InputStream bodyStream = response.getBody(); + if (bodyStream != null) { + lazyBody = FileCopyUtils.copyToByteArray(bodyStream); + } + else { + lazyBody = new byte[0]; + } + } + return new ByteArrayInputStream(lazyBody); + } + + public HttpHeaders getHeaders() { + return response.getHeaders(); + } + + public String getStatusText() throws IOException { + return response.getStatusText(); + } + + public void close() { + response.close(); + } + + public int getRawStatusCode() throws IOException { + return this.getStatusCode().value(); + } + }; + + try { + HttpMessageConverterExtractor extractor = new HttpMessageConverterExtractor( + OAuth2Exception.class, messageConverters); + try { + OAuth2Exception oauth2Exception = extractor.extractData(bufferedResponse); + if (oauth2Exception != null) { + // gh-875 + if (oauth2Exception.getClass() == UserDeniedAuthorizationException.class && + bufferedResponse.getStatusCode().equals(HttpStatus.FORBIDDEN)) { + oauth2Exception = new OAuth2AccessDeniedException(oauth2Exception.getMessage()); + } + // If we can get an OAuth2Exception, it is likely to have more information + // than the header does, so just re-throw it here. + throw oauth2Exception; + } + } + catch (RestClientException e) { + // ignore + } + catch (HttpMessageConversionException e){ + // ignore + } + + // first try: www-authenticate error + List authenticateHeaders = bufferedResponse.getHeaders().get("WWW-Authenticate"); + if (authenticateHeaders != null) { + for (String authenticateHeader : authenticateHeaders) { + maybeThrowExceptionFromHeader(authenticateHeader, OAuth2AccessToken.BEARER_TYPE); + maybeThrowExceptionFromHeader(authenticateHeader, OAuth2AccessToken.OAUTH2_TYPE); + } + } + + // then delegate to the custom handler + errorHandler.handleError(bufferedResponse); + } + catch (InvalidTokenException ex) { + // Special case: an invalid token can be renewed so tell the caller what to do + throw new AccessTokenRequiredException(resource); + } + catch (OAuth2Exception ex) { + if (!ex.getClass().equals(OAuth2Exception.class)) { + // There is more information here than the caller would get from an HttpClientErrorException so + // rethrow + throw ex; + } + // This is not an exception that is really understood, so allow our delegate + // to handle it in a non-oauth way + errorHandler.handleError(bufferedResponse); } } - - // then delegate to the custom handler - errorHandler.handleError(response); } private void maybeThrowExceptionFromHeader(String authenticateHeader, String headerType) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/StringSplitUtils.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/StringSplitUtils.java index 8670961a9..e2c592e73 100755 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/StringSplitUtils.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/StringSplitUtils.java @@ -10,7 +10,12 @@ /** * Provides several String manipulation methods. Copied from deleted org.springframework.security.util.StringSplitUtils + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * */ +@Deprecated public class StringSplitUtils { private static final String[] EMPTY_STRING_ARRAY = new String[0]; @@ -90,8 +95,9 @@ public static Map splitEachArrayElementAndCreateMap(String[] arr * Splits a given string on the given separator character, skips the contents of quoted substrings * when looking for separators. * Introduced for use in DigestProcessingFilter (see SEC-506). - *

+ *

* This was copied and modified from commons-lang StringUtils + *

*/ public static String[] splitIgnoringQuotes(String str, char separatorChar) { if (str == null) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/BaseOAuth2ProtectedResourceDetails.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/BaseOAuth2ProtectedResourceDetails.java index 3608eb1f4..788b0b45e 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/BaseOAuth2ProtectedResourceDetails.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/BaseOAuth2ProtectedResourceDetails.java @@ -7,9 +7,13 @@ import org.springframework.util.StringUtils; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class BaseOAuth2ProtectedResourceDetails implements OAuth2ProtectedResourceDetails { private String id; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/OAuth2AccessDeniedException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/OAuth2AccessDeniedException.java index 203790433..0037c7d40 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/OAuth2AccessDeniedException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/OAuth2AccessDeniedException.java @@ -3,12 +3,17 @@ import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; /** - * When access is denied we usually want a 403, but we want the same treatment as all teh other OAuth2Exception types, + * When access is denied we usually want a 403, but we want the same treatment as all the other OAuth2Exception types, * so this is not a Spring Security AccessDeniedException. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@SuppressWarnings("serial") +@Deprecated public class OAuth2AccessDeniedException extends OAuth2Exception { private OAuth2ProtectedResourceDetails resource; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/OAuth2ProtectedResourceDetails.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/OAuth2ProtectedResourceDetails.java index a539f758d..145ae383d 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/OAuth2ProtectedResourceDetails.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/OAuth2ProtectedResourceDetails.java @@ -6,10 +6,14 @@ /** * Details for an OAuth2-protected resource. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public interface OAuth2ProtectedResourceDetails { /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/UserApprovalRequiredException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/UserApprovalRequiredException.java index e18cf49fa..d7505f946 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/UserApprovalRequiredException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/UserApprovalRequiredException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -20,10 +20,15 @@ /** * Exception indicating that user approval is required, with some indication of how to signal the approval. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@SuppressWarnings("serial") +@Deprecated public class UserApprovalRequiredException extends RuntimeException { private final String approvalUri; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/UserRedirectRequiredException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/UserRedirectRequiredException.java index 3d3870efa..6b8a60b9b 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/UserRedirectRequiredException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/resource/UserRedirectRequiredException.java @@ -4,9 +4,14 @@ /** * Special exception thrown when a user redirect is required in order to obtain an OAuth2 access token. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class UserRedirectRequiredException extends RuntimeException { private final String redirectUri; @@ -46,7 +51,6 @@ public Map getRequestParams() { * * @return The key to the state to preserve. */ - // TODO: is this obsolete or in the wrong place? public String getStateKey() { return stateKey; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/BeforeOAuth2Context.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/BeforeOAuth2Context.java index f190be6ea..a289eb6ee 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/BeforeOAuth2Context.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/BeforeOAuth2Context.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -21,11 +21,15 @@ * Marker annotation for methods to be run before the OAuth2Context is setup by the {@link OAuth2ContextSetup} rule, and * consequently before the regular JUnit @Before methods, which are executed only after the * OAuth2Context is setup. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) +@Deprecated public @interface BeforeOAuth2Context { } \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/OAuth2ContextConfiguration.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/OAuth2ContextConfiguration.java index f09930fa3..06a68b5db 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/OAuth2ContextConfiguration.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/OAuth2ContextConfiguration.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -30,12 +30,16 @@ * Annotation to signal that an OAuth2 authentication should be created and and provided to the enclosing scope (method * or class). Used at the class level it will apply to all test methods (and {@link BeforeOAuth2Context} initializers). * Used at the method level it will apply only to the method, overriding any value found on the enclosing class. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) +@Deprecated public @interface OAuth2ContextConfiguration { /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/OAuth2ContextSetup.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/OAuth2ContextSetup.java index 03ad890d6..9633ef9cf 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/OAuth2ContextSetup.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/OAuth2ContextSetup.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -15,6 +15,7 @@ import java.io.IOException; import java.lang.reflect.Constructor; import java.net.HttpURLConnection; +import java.net.URI; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; @@ -22,6 +23,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.http.client.config.CookieSpecs; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.config.RequestConfig.Builder; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.protocol.HttpContext; import org.hamcrest.CoreMatchers; import org.junit.Assert; import org.junit.internal.AssumptionViolatedException; @@ -32,7 +38,9 @@ import org.junit.runners.model.TestClass; import org.springframework.beans.BeanUtils; import org.springframework.core.env.Environment; +import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; import org.springframework.security.oauth2.client.OAuth2ClientContext; @@ -44,15 +52,16 @@ import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.util.ClassUtils; -import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestOperations; /** *

- * A rule that sets up an OAuth2 context for tests and makes the access token available inside a test method. In - * combination with the {@link OAuth2ContextConfiguration} annotation provides a number of different strategies for - * configuring an {@link OAuth2ProtectedResourceDetails} instance that will be used to create the OAuth2 context for - * tests. Example: + * A rule that sets up an OAuth2 context for tests and makes the access token available + * inside a test method. In combination with the {@link OAuth2ContextConfiguration} + * annotation provides a number of different strategies for configuring an + * {@link OAuth2ProtectedResourceDetails} instance that will be used to create the OAuth2 + * context for tests. Example: *

* *
@@ -60,16 +69,19 @@
  * public class MyIntegrationTests implements RestTemplateHolder {
  * 
  * 	@Rule
- * 	public OAuth2ContextSetup context = OAuth2ContextSetup.withEnvironment(this, TestEnvironment.instance());
+ * 	public OAuth2ContextSetup context = OAuth2ContextSetup.withEnvironment(this,
+ * 			TestEnvironment.instance());
  * 
  * 	@Test
  * 	public void testSomethingWithClientCredentials() {
- * 		// This call will be authenticated with the client credentials in MyClientDetailsResource
- * 		getRestTemplate().getForObject("http://myserver/resource", String.class);
+ * 		// This call will be authenticated with the client credentials in
+ * 		// MyClientDetailsResource
+ * 		getRestTemplate().getForObject("https://myserver/resource", String.class);
  * 	}
  * 
  * 	// This class is used to initialize the OAuth2 context for the test methods.
- * 	static class MyClientDetailsResource extends ResourceOwnerPasswordProtectedResourceDetails {
+ * 	static class MyClientDetailsResource extends
+ * 			ResourceOwnerPasswordProtectedResourceDetails {
  * 		public MyClientDetailsResource(Environment environment) {
  *             ... do stuff with environment to initialize the password credentials
  *         }
@@ -80,10 +92,15 @@
  * 
  * @see OAuth2ContextConfiguration
  * @see BeforeOAuth2Context
- * 
+ *
+ * 

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@SuppressWarnings("deprecation") +@Deprecated public class OAuth2ContextSetup extends TestWatchman { private static Log logger = LogFactory.getLog(OAuth2ContextSetup.class); @@ -109,33 +126,40 @@ public class OAuth2ContextSetup extends TestWatchman { private final Environment environment; /** - * Create a new client that can inject an Environment into its protected resource details. + * Create a new client that can inject an Environment into its protected resource + * details. * - * @param clientHolder receives an OAuth2RestTemplate with the authenticated client for the duration of a test + * @param clientHolder receives an OAuth2RestTemplate with the authenticated client + * for the duration of a test * @param environment a Spring Environment that can be used to initialize the client * * @return a rule that wraps test methods in an OAuth2 context */ - public static OAuth2ContextSetup withEnvironment(RestTemplateHolder clientHolder, Environment environment) { + public static OAuth2ContextSetup withEnvironment(RestTemplateHolder clientHolder, + Environment environment) { return new OAuth2ContextSetup(clientHolder, null, environment); } /** - * Create a new client that can inject a {@link TestAccounts} instance into its protected resource details. + * Create a new client that can inject a {@link TestAccounts} instance into its + * protected resource details. * - * @param clientHolder receives an OAuth2RestTemplate with the authenticated client for the duration of a test - * @param testAccounts a test account generator that can be used to initialize the client + * @param clientHolder receives an OAuth2RestTemplate with the authenticated client + * for the duration of a test + * @param testAccounts a test account generator that can be used to initialize the + * client * * @return a rule that wraps test methods in an OAuth2 context */ - public static OAuth2ContextSetup withTestAccounts(RestTemplateHolder clientHolder, TestAccounts testAccounts) { + public static OAuth2ContextSetup withTestAccounts(RestTemplateHolder clientHolder, + TestAccounts testAccounts) { return new OAuth2ContextSetup(clientHolder, testAccounts, null); } /** - * Create a new client that knows how to create its protected resource with no externalization help. Typically it - * will use resource details which accept an instance of the current test case (downcasting it from Object). For - * example + * Create a new client that knows how to create its protected resource with no + * externalization help. Typically it will use resource details which accept an + * instance of the current test case (downcasting it from Object). For example * *

 	 * static class MyClientDetailsResource extends ClientCredentialsProtectedResourceDetails {
@@ -146,7 +170,8 @@ public static OAuth2ContextSetup withTestAccounts(RestTemplateHolder clientHolde
 	 * }
 	 * 
* - * @param clientHolder receives an OAuth2RestTemplate with the authenticated client for the duration of a test + * @param clientHolder receives an OAuth2RestTemplate with the authenticated client + * for the duration of a test * * @return a rule that wraps test methods in an OAuth2 context */ @@ -154,7 +179,8 @@ public static OAuth2ContextSetup standard(RestTemplateHolder clientHolder) { return new OAuth2ContextSetup(clientHolder, null, null); } - private OAuth2ContextSetup(RestTemplateHolder clientHolder, TestAccounts testAccounts, Environment environment) { + private OAuth2ContextSetup(RestTemplateHolder clientHolder, + TestAccounts testAccounts, Environment environment) { this.clientHolder = clientHolder; this.testAccounts = testAccounts; this.environment = environment; @@ -201,8 +227,9 @@ public void setParameters(Map parameters) { } /** - * Get the current access token. Should be available inside a test method as long as a resource has been setup with - * {@link OAuth2ContextConfiguration @OAuth2ContextConfiguration}. + * Get the current access token. Should be available inside a test method as long as a + * resource has been setup with {@link OAuth2ContextConfiguration + * @OAuth2ContextConfiguration}. * * @return the current access token initializing it if necessary */ @@ -213,10 +240,10 @@ public OAuth2AccessToken getAccessToken() { if (accessToken != null) { return accessToken; } + if (accessTokenProvider != null) { + client.setAccessTokenProvider(accessTokenProvider); + } try { - if (accessTokenProvider != null) { - client.setAccessTokenProvider(accessTokenProvider); - } return client.getAccessToken(); } catch (OAuth2AccessDeniedException e) { @@ -262,7 +289,8 @@ public OAuth2ClientContext getOAuth2ClientContext() { private void initializeIfNecessary(FrameworkMethod method, final Object target) { final TestClass testClass = new TestClass(target.getClass()); - OAuth2ContextConfiguration contextConfiguration = findOAuthContextConfiguration(method, testClass); + OAuth2ContextConfiguration contextConfiguration = findOAuthContextConfiguration( + method, testClass); if (contextConfiguration == null) { // Nothing to do return; @@ -272,7 +300,8 @@ private void initializeIfNecessary(FrameworkMethod method, final Object target) this.resource = creatResource(target, contextConfiguration); - final List befores = testClass.getAnnotatedMethods(BeforeOAuth2Context.class); + final List befores = testClass + .getAnnotatedMethods(BeforeOAuth2Context.class); if (!befores.isEmpty()) { logger.debug("Running @BeforeOAuth2Context methods"); @@ -281,13 +310,16 @@ private void initializeIfNecessary(FrameworkMethod method, final Object target) RestOperations savedServerClient = clientHolder.getRestTemplate(); - OAuth2ContextConfiguration beforeConfiguration = findOAuthContextConfiguration(before, testClass); + OAuth2ContextConfiguration beforeConfiguration = findOAuthContextConfiguration( + before, testClass); if (beforeConfiguration != null) { - OAuth2ProtectedResourceDetails resource = creatResource(target, beforeConfiguration); + OAuth2ProtectedResourceDetails resource = creatResource(target, + beforeConfiguration); AccessTokenRequest beforeRequest = new DefaultAccessTokenRequest(); beforeRequest.setAll(parameters); - OAuth2RestTemplate client = createRestTemplate(resource, beforeRequest); + OAuth2RestTemplate client = createRestTemplate(resource, + beforeRequest); clientHolder.setRestTemplate(client); } @@ -309,6 +341,9 @@ public void evaluate() { catch (RuntimeException e) { throw e; } + catch (AssertionError e) { + throw e; + } catch (Throwable e) { logger.debug("Exception in befores", e); Assert.assertThat(e, CoreMatchers.not(CoreMatchers.anything())); @@ -323,24 +358,16 @@ public void evaluate() { } - private OAuth2RestTemplate createRestTemplate(OAuth2ProtectedResourceDetails resource, AccessTokenRequest request) { + private OAuth2RestTemplate createRestTemplate( + OAuth2ProtectedResourceDetails resource, AccessTokenRequest request) { OAuth2ClientContext context = new DefaultOAuth2ClientContext(request); OAuth2RestTemplate client = new OAuth2RestTemplate(resource, context); - client.setRequestFactory(new SimpleClientHttpRequestFactory() { - @Override - protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { - super.prepareConnection(connection, httpMethod); - connection.setInstanceFollowRedirects(false); - } - }); - client.setErrorHandler(new ResponseErrorHandler() { + setupConnectionFactory(client); + client.setErrorHandler(new DefaultResponseErrorHandler() { // Pass errors through in response entity for status code analysis public boolean hasError(ClientHttpResponse response) throws IOException { return false; } - - public void handleError(ClientHttpResponse response) throws IOException { - } }); if (accessTokenProvider != null) { client.setAccessTokenProvider(accessTokenProvider); @@ -348,13 +375,46 @@ public void handleError(ClientHttpResponse response) throws IOException { return client; } - private OAuth2ProtectedResourceDetails creatResource(Object target, OAuth2ContextConfiguration contextLoader) { + private void setupConnectionFactory(OAuth2RestTemplate client) { + if (Boolean.getBoolean("http.components.enabled") + && ClassUtils.isPresent("org.apache.http.client.config.RequestConfig", + null)) { + client.setRequestFactory(new HttpComponentsClientHttpRequestFactory() { + @Override + protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) { + HttpClientContext context = HttpClientContext.create(); + context.setRequestConfig(getRequestConfig()); + return context; + } + + protected RequestConfig getRequestConfig() { + Builder builder = RequestConfig.custom() + .setCookieSpec(CookieSpecs.IGNORE_COOKIES) + .setAuthenticationEnabled(false).setRedirectsEnabled(false); + return builder.build(); + } + }); + } + else { + client.setRequestFactory(new SimpleClientHttpRequestFactory() { + @Override + protected void prepareConnection(HttpURLConnection connection, + String httpMethod) throws IOException { + super.prepareConnection(connection, httpMethod); + connection.setInstanceFollowRedirects(false); + } + }); + } + } + + private OAuth2ProtectedResourceDetails creatResource(Object target, + OAuth2ContextConfiguration contextLoader) { Class type = contextLoader.value(); if (type == OAuth2ProtectedResourceDetails.class) { type = contextLoader.resource(); } - Constructor constructor = ClassUtils.getConstructorIfAvailable(type, - TestAccounts.class); + Constructor constructor = ClassUtils + .getConstructorIfAvailable(type, TestAccounts.class); if (constructor != null && testAccounts != null) { return BeanUtils.instantiateClass(constructor, testAccounts); } @@ -370,13 +430,17 @@ private OAuth2ProtectedResourceDetails creatResource(Object target, OAuth2Contex return BeanUtils.instantiate(type); } - private OAuth2ContextConfiguration findOAuthContextConfiguration(FrameworkMethod method, TestClass testClass) { - OAuth2ContextConfiguration methodConfiguration = method.getAnnotation(OAuth2ContextConfiguration.class); + private OAuth2ContextConfiguration findOAuthContextConfiguration( + FrameworkMethod method, TestClass testClass) { + OAuth2ContextConfiguration methodConfiguration = method + .getAnnotation(OAuth2ContextConfiguration.class); if (methodConfiguration != null) { return methodConfiguration; } - if (testClass.getJavaClass().isAnnotationPresent(OAuth2ContextConfiguration.class)) { - return testClass.getJavaClass().getAnnotation(OAuth2ContextConfiguration.class); + if (testClass.getJavaClass() + .isAnnotationPresent(OAuth2ContextConfiguration.class)) { + return testClass.getJavaClass().getAnnotation( + OAuth2ContextConfiguration.class); } return null; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/RestTemplateHolder.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/RestTemplateHolder.java index 98d9ce81b..db8f9b513 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/RestTemplateHolder.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/RestTemplateHolder.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -15,11 +15,15 @@ import org.springframework.web.client.RestOperations; /** - * Marker interface for an object that has a getter and setter for a {@link RestTemplate}. - * + * Marker interface for an object that has a getter and setter for a {@link RestOperations}. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface RestTemplateHolder { void setRestTemplate(RestOperations restTemplate); diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/TestAccounts.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/TestAccounts.java index e7ca361d0..8d9706e53 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/TestAccounts.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/test/TestAccounts.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -17,9 +17,13 @@ import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface TestAccounts { String getUserName(); diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenProvider.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenProvider.java index 1a135e84e..a803c3df6 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenProvider.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenProvider.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,15 +19,19 @@ import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.resource.UserApprovalRequiredException; import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; -import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2RefreshToken; /** - * A manager for an , which knows how to obtain an access token for a specific resources. - * + * A strategy which knows how to obtain an access token for a specific resource. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public interface AccessTokenProvider { /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenProviderChain.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenProviderChain.java index 5597f0c36..51cc6c9f3 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenProviderChain.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenProviderChain.java @@ -1,11 +1,11 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -15,6 +15,7 @@ */ package org.springframework.security.oauth2.client.token; +import java.util.Calendar; import java.util.Collections; import java.util.List; @@ -26,32 +27,40 @@ import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; /** - * A chain of OAuth2 access token providers. This implementation will iterate through its chain to find the first - * provider that supports the resource and use it to obtain the access token. Note that the order of the chain is - * relevant. - * + * A chain of OAuth2 access token providers. This implementation will iterate through its + * chain to find the first provider that supports the resource and use it to obtain the + * access token. Note that the order of the chain is relevant. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ -public class AccessTokenProviderChain extends OAuth2AccessTokenSupport implements AccessTokenProvider { +@Deprecated +public class AccessTokenProviderChain extends OAuth2AccessTokenSupport + implements AccessTokenProvider { private final List chain; private ClientTokenServices clientTokenServices; + private int clockSkew = 30; + public AccessTokenProviderChain(List chain) { - this.chain = chain == null ? Collections. emptyList() : Collections - .unmodifiableList(chain); + this.chain = chain == null ? Collections. emptyList() + : Collections.unmodifiableList(chain); } /** * Token services for long-term persistence of access tokens. - * + * * @param clientTokenServices the clientTokenServices to set */ public void setClientTokenServices(ClientTokenServices clientTokenServices) { @@ -76,7 +85,8 @@ public boolean supportsRefresh(OAuth2ProtectedResourceDetails resource) { return false; } - public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails resource, AccessTokenRequest request) + public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails resource, + AccessTokenRequest request) throws UserRedirectRequiredException, AccessDeniedException { OAuth2AccessToken accessToken = null; @@ -97,12 +107,12 @@ public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails resour } if (existingToken != null) { - if (existingToken.isExpired()) { + if (hasTokenExpired(existingToken)) { if (clientTokenServices != null) { clientTokenServices.removeAccessToken(resource, auth); } OAuth2RefreshToken refreshToken = existingToken.getRefreshToken(); - if (refreshToken != null) { + if (refreshToken != null && !resource.isClientOnly()) { accessToken = refreshAccessToken(resource, refreshToken, request); } } @@ -118,19 +128,22 @@ public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails resour accessToken = obtainNewAccessTokenInternal(resource, request); if (accessToken == null) { - throw new IllegalStateException("An OAuth 2 access token must be obtained or an exception thrown."); + throw new IllegalStateException( + "An OAuth 2 access token must be obtained or an exception thrown."); } } - if (clientTokenServices != null) { + if (clientTokenServices != null + && (resource.isClientOnly() || auth != null && auth.isAuthenticated())) { clientTokenServices.saveAccessToken(resource, auth, accessToken); } return accessToken; } - protected OAuth2AccessToken obtainNewAccessTokenInternal(OAuth2ProtectedResourceDetails details, - AccessTokenRequest request) throws UserRedirectRequiredException, AccessDeniedException { + protected OAuth2AccessToken obtainNewAccessTokenInternal( + OAuth2ProtectedResourceDetails details, AccessTokenRequest request) + throws UserRedirectRequiredException, AccessDeniedException { if (request.isError()) { // there was an oauth error... @@ -143,27 +156,55 @@ protected OAuth2AccessToken obtainNewAccessTokenInternal(OAuth2ProtectedResource } } - throw new OAuth2AccessDeniedException("Unable to obtain a new access token for resource '" + details.getId() - + "'. The provider manager is not configured to support it.", details); + throw new OAuth2AccessDeniedException( + "Unable to obtain a new access token for resource '" + details.getId() + + "'. The provider manager is not configured to support it.", + details); } /** * Obtain a new access token for the specified resource using the refresh token. - * + * * @param resource The resource. * @param refreshToken The refresh token. * @return The access token, or null if failed. * @throws UserRedirectRequiredException */ public OAuth2AccessToken refreshAccessToken(OAuth2ProtectedResourceDetails resource, - OAuth2RefreshToken refreshToken, AccessTokenRequest request) throws UserRedirectRequiredException { + OAuth2RefreshToken refreshToken, AccessTokenRequest request) + throws UserRedirectRequiredException { for (AccessTokenProvider tokenProvider : chain) { if (tokenProvider.supportsRefresh(resource)) { - return tokenProvider.refreshAccessToken(resource, refreshToken, request); + DefaultOAuth2AccessToken refreshedAccessToken = new DefaultOAuth2AccessToken( + tokenProvider.refreshAccessToken(resource, refreshToken, + request)); + if (refreshedAccessToken.getRefreshToken() == null) { + // Fixes gh-712 + refreshedAccessToken.setRefreshToken(refreshToken); + } + return refreshedAccessToken; } } - throw new OAuth2AccessDeniedException("Unable to obtain a new access token for resource '" + resource.getId() - + "'. The provider manager is not configured to support it.", resource); + throw new OAuth2AccessDeniedException( + "Unable to obtain a new access token for resource '" + resource.getId() + + "'. The provider manager is not configured to support it.", + resource); } + /** + * Checks if the given {@link OAuth2AccessToken access token} should be considered to have expired based on the + * token's expiration time and the clock skew. + * + * @param token the token to be checked + * @return true if the token should be considered expired, false otherwise + */ + private boolean hasTokenExpired(OAuth2AccessToken token) { + Calendar now = Calendar.getInstance(); + Calendar expiresAt = (Calendar) now.clone(); + if (token.getExpiration() != null) { + expiresAt.setTime(token.getExpiration()); + expiresAt.add(Calendar.SECOND, -this.clockSkew); + } + return now.after(expiresAt); + } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenRequest.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenRequest.java index 7993411a7..02c23baf8 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenRequest.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenRequest.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -15,9 +15,18 @@ */ package org.springframework.security.oauth2.client.token; +import java.util.List; +import java.util.Map; + import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.util.MultiValueMap; +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + */ +@Deprecated public interface AccessTokenRequest extends MultiValueMap { OAuth2AccessToken getExistingToken(); @@ -45,5 +54,9 @@ public interface AccessTokenRequest extends MultiValueMap { void setCookie(String cookie); String getCookie(); + + void setHeaders(Map> headers); + + Map> getHeaders(); } \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/ClientKeyGenerator.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/ClientKeyGenerator.java index 9d8eb0fd0..cdc58adf2 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/ClientKeyGenerator.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/ClientKeyGenerator.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -18,9 +18,13 @@ import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface ClientKeyGenerator { /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/ClientTokenServices.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/ClientTokenServices.java index 95811454b..2f3937b58 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/ClientTokenServices.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/ClientTokenServices.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -19,9 +19,13 @@ import org.springframework.security.oauth2.common.OAuth2AccessToken; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface ClientTokenServices { /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/DefaultAccessTokenRequest.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/DefaultAccessTokenRequest.java index 9e3347725..3136fd6b4 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/DefaultAccessTokenRequest.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/DefaultAccessTokenRequest.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -15,24 +15,24 @@ */ package org.springframework.security.oauth2.client.token; -import java.io.Serializable; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; - import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import java.io.Serializable; +import java.util.*; + /** * Local context for an access token request encapsulating the parameters that are sent by the client requesting the * token, as opposed to the more static variables representing the client itself and the resource being targeted. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class DefaultAccessTokenRequest implements AccessTokenRequest, Serializable { private static final long serialVersionUID = 914967629530462926L; @@ -47,6 +47,8 @@ public class DefaultAccessTokenRequest implements AccessTokenRequest, Serializab private String cookie; + private Map> headers = new LinkedMultiValueMap(); + public DefaultAccessTokenRequest() { } @@ -112,6 +114,14 @@ public void setCookie(String cookie) { public String getCookie() { return cookie; } + + public void setHeaders(Map> headers) { + this.headers = headers; + } + + public Map> getHeaders() { + return headers; + } public void setExistingToken(OAuth2AccessToken existingToken) { this.existingToken = existingToken; @@ -129,6 +139,18 @@ public void add(String key, String value) { parameters.add(key, value); } + public void addAll(String key, List values) { + for (String value : values) { + this.add(key, value); + } + } + + public void addAll(MultiValueMap map) { + for (Entry> entry : map.entrySet()) { + this.addAll(entry.getKey(), entry.getValue()); + } + } + public void set(String key, String value) { parameters.set(key, value); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/DefaultClientKeyGenerator.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/DefaultClientKeyGenerator.java index 5d5264983..a083ceacf 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/DefaultClientKeyGenerator.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/DefaultClientKeyGenerator.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -25,10 +25,14 @@ /** * Basic key generator taking into account the client id, scope and username (principal name) if they exist. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class DefaultClientKeyGenerator implements ClientKeyGenerator { private static final String CLIENT_ID = "client_id"; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/DefaultRequestEnhancer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/DefaultRequestEnhancer.java new file mode 100644 index 000000000..322d58ec7 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/DefaultRequestEnhancer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013-2014 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.client.token; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.http.HttpHeaders; +import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; +import org.springframework.util.MultiValueMap; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + */ +@Deprecated +public class DefaultRequestEnhancer implements RequestEnhancer { + + private Set parameterIncludes = Collections.emptySet(); + + public void setParameterIncludes(Collection parameterIncludes) { + this.parameterIncludes = new LinkedHashSet(parameterIncludes); + } + + @Override + public void enhance(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, MultiValueMap form, HttpHeaders headers) { + for (String include : parameterIncludes) { + if (request.containsKey(include)) { + form.set(include, request.getFirst(include)); + } + } + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/JdbcClientTokenServices.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/JdbcClientTokenServices.java index 05c1f5271..4f4474541 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/JdbcClientTokenServices.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/JdbcClientTokenServices.java @@ -20,9 +20,13 @@ /** * Implementation of token services that stores tokens in a database for retrieval by client applications. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer */ +@Deprecated public class JdbcClientTokenServices implements ClientTokenServices { private static final Log LOG = LogFactory.getLog(JdbcClientTokenServices.class); @@ -75,10 +79,11 @@ public OAuth2AccessToken mapRow(ResultSet rs, int rowNum) throws SQLException { public void saveAccessToken(OAuth2ProtectedResourceDetails resource, Authentication authentication, OAuth2AccessToken accessToken) { removeAccessToken(resource, authentication); + String name = authentication==null ? null : authentication.getName(); jdbcTemplate.update( insertAccessTokenSql, new Object[] { accessToken.getValue(), new SqlLobValue(SerializationUtils.serialize(accessToken)), - keyGenerator.extractKey(resource, authentication), authentication.getName(), + keyGenerator.extractKey(resource, authentication), name, resource.getClientId() }, new int[] { Types.VARCHAR, Types.BLOB, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR }); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/OAuth2AccessTokenSupport.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/OAuth2AccessTokenSupport.java index d0fe5c7c9..c31822633 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/OAuth2AccessTokenSupport.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/OAuth2AccessTokenSupport.java @@ -1,11 +1,5 @@ package org.springframework.security.oauth2.client.token; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.http.HttpHeaders; @@ -13,6 +7,7 @@ import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.converter.FormHttpMessageConverter; @@ -36,19 +31,29 @@ import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** * Base support logic for obtaining access tokens. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public abstract class OAuth2AccessTokenSupport { protected final Log logger = LogFactory.getLog(getClass()); private static final FormHttpMessageConverter FORM_MESSAGE_CONVERTER = new FormHttpMessageConverter(); - private RestOperations restTemplate; + private volatile RestOperations restTemplate; private List> messageConverters; @@ -56,12 +61,32 @@ public abstract class OAuth2AccessTokenSupport { private ResponseErrorHandler responseErrorHandler = new AccessTokenErrorHandler(); + private List interceptors = new ArrayList(); + + private RequestEnhancer tokenRequestEnhancer = new DefaultRequestEnhancer(); + + /** + * Sets the request interceptors that this accessor should use. + */ + public void setInterceptors(List interceptors) { + this.interceptors = interceptors; + } + + /** + * A custom enhancer for the access token request + * @param tokenRequestEnhancer + */ + public void setTokenRequestEnhancer(RequestEnhancer tokenRequestEnhancer) { + this.tokenRequestEnhancer = tokenRequestEnhancer; + } + private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory() { @Override protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { super.prepareConnection(connection, httpMethod); connection.setInstanceFollowRedirects(false); + connection.setUseCaches(false); } }; @@ -72,6 +97,7 @@ protected RestOperations getRestTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setErrorHandler(getResponseErrorHandler()); restTemplate.setRequestFactory(requestFactory); + restTemplate.setInterceptors(interceptors); this.restTemplate = restTemplate; } } @@ -92,15 +118,28 @@ public void setMessageConverters(List> messageConverters this.messageConverters.add(new FormOAuth2ExceptionHttpMessageConverter()); } - protected OAuth2AccessToken retrieveToken(MultiValueMap form, HttpHeaders headers, - OAuth2ProtectedResourceDetails resource) throws OAuth2AccessDeniedException { + protected OAuth2AccessToken retrieveToken(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, + MultiValueMap form, HttpHeaders headers) throws OAuth2AccessDeniedException { try { // Prepare headers and form before going into rest template call in case the URI is affected by the result authenticationHandler.authenticateTokenRequest(resource, form, headers); - + // Opportunity to customize form and headers + tokenRequestEnhancer.enhance(request, resource, form, headers); + final AccessTokenRequest copy = request; + + final ResponseExtractor delegate = getResponseExtractor(); + ResponseExtractor extractor = new ResponseExtractor() { + @Override + public OAuth2AccessToken extractData(ClientHttpResponse response) throws IOException { + if (response.getHeaders().containsKey("Set-Cookie")) { + copy.setCookie(response.getHeaders().getFirst("Set-Cookie")); + } + return delegate.extractData(response); + } + }; return getRestTemplate().execute(getAccessTokenUri(resource, form), getHttpMethod(), - getRequestCallback(resource, form, headers), getResponseExtractor(), form.toSingleValueMap()); + getRequestCallback(resource, form, headers), extractor , form.toSingleValueMap()); } catch (OAuth2Exception oe) { @@ -183,7 +222,9 @@ public void doWithRequest(ClientHttpRequest request) throws IOException { request.getHeaders().putAll(this.headers); request.getHeaders().setAccept( Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_FORM_URLENCODED)); - logger.debug("Encoding and sending form: " + form); + if (logger.isDebugEnabled()) { + logger.debug("Encoding and sending form: " + form); + } FORM_MESSAGE_CONVERTER.write(this.form, MediaType.APPLICATION_FORM_URLENCODED, request); } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/RequestEnhancer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/RequestEnhancer.java new file mode 100644 index 000000000..c49d1a841 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/RequestEnhancer.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013-2014 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.client.token; + +import org.springframework.http.HttpHeaders; +import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; +import org.springframework.util.MultiValueMap; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + */ +@Deprecated +public interface RequestEnhancer { + + void enhance(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, MultiValueMap form, + HttpHeaders headers); + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/auth/ClientAuthenticationHandler.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/auth/ClientAuthenticationHandler.java index 7f300019c..91b625653 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/auth/ClientAuthenticationHandler.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/auth/ClientAuthenticationHandler.java @@ -1,3 +1,15 @@ +/* + * Copyright 2013-2014 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.client.token.auth; import org.springframework.http.HttpHeaders; @@ -6,10 +18,14 @@ /** * Logic for handling client authentication. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public interface ClientAuthenticationHandler { /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/auth/DefaultClientAuthenticationHandler.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/auth/DefaultClientAuthenticationHandler.java index 88a0a6fa0..331c26f4c 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/auth/DefaultClientAuthenticationHandler.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/auth/DefaultClientAuthenticationHandler.java @@ -11,10 +11,14 @@ /** * Default implementation of the client authentication handler. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class DefaultClientAuthenticationHandler implements ClientAuthenticationHandler { public void authenticateTokenRequest(OAuth2ProtectedResourceDetails resource, MultiValueMap form, diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/client/ClientCredentialsAccessTokenProvider.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/client/ClientCredentialsAccessTokenProvider.java index d97cfdde5..4d7c664c7 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/client/ClientCredentialsAccessTokenProvider.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/client/ClientCredentialsAccessTokenProvider.java @@ -13,14 +13,19 @@ import org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; /** * Provider for obtaining an oauth2 access token by using client credentials. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer */ +@Deprecated public class ClientCredentialsAccessTokenProvider extends OAuth2AccessTokenSupport implements AccessTokenProvider { public boolean supportsResource(OAuth2ProtectedResourceDetails resource) { @@ -41,15 +46,14 @@ public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails detail throws UserRedirectRequiredException, AccessDeniedException, OAuth2AccessDeniedException { ClientCredentialsResourceDetails resource = (ClientCredentialsResourceDetails) details; - return retrieveToken(getParametersForTokenRequest(resource), new HttpHeaders(), resource); + return retrieveToken(request, resource, getParametersForTokenRequest(resource), new HttpHeaders()); } private MultiValueMap getParametersForTokenRequest(ClientCredentialsResourceDetails resource) { MultiValueMap form = new LinkedMultiValueMap(); - form.set("grant_type", "client_credentials"); - form.set("client_id", resource.getClientId()); + form.set(OAuth2Utils.GRANT_TYPE, "client_credentials"); if (resource.isScoped()) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/client/ClientCredentialsResourceDetails.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/client/ClientCredentialsResourceDetails.java index 9ddc01b8d..fe2e1a001 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/client/ClientCredentialsResourceDetails.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/client/ClientCredentialsResourceDetails.java @@ -3,8 +3,12 @@ import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer */ +@Deprecated public class ClientCredentialsResourceDetails extends BaseOAuth2ProtectedResourceDetails { public ClientCredentialsResourceDetails() { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeAccessTokenProvider.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeAccessTokenProvider.java index ab72bad1c..4e48c9685 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeAccessTokenProvider.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeAccessTokenProvider.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -20,7 +20,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -52,26 +52,63 @@ import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; import org.springframework.security.oauth2.client.token.AccessTokenProvider; import org.springframework.security.oauth2.client.token.AccessTokenRequest; +import org.springframework.security.oauth2.client.token.DefaultRequestEnhancer; import org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport; +import org.springframework.security.oauth2.client.token.RequestEnhancer; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; import org.springframework.security.oauth2.common.util.OAuth2Utils; -import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseExtractor; /** * Provider for obtaining an oauth2 access token by using an authorization code. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class AuthorizationCodeAccessTokenProvider extends OAuth2AccessTokenSupport implements AccessTokenProvider { private StateKeyGenerator stateKeyGenerator = new DefaultStateKeyGenerator(); + private String scopePrefix = OAuth2Utils.SCOPE_PREFIX; + + private RequestEnhancer authorizationRequestEnhancer = new DefaultRequestEnhancer(); + + private boolean stateMandatory = true; + + /** + * Flag to say that the use of state parameter is mandatory. + * + * @param stateMandatory the flag value (default true) + */ + public void setStateMandatory(boolean stateMandatory) { + this.stateMandatory = stateMandatory; + } + + /** + * A custom enhancer for the authorization request + * @param authorizationRequestEnhancer + */ + public void setAuthorizationRequestEnhancer(RequestEnhancer authorizationRequestEnhancer) { + this.authorizationRequestEnhancer = authorizationRequestEnhancer; + } + + /** + * Prefix for scope approval parameters. + * + * @param scopePrefix + */ + public void setScopePrefix(String scopePrefix) { + this.scopePrefix = scopePrefix; + } + /** * @param stateKeyGenerator the stateKeyGenerator to set */ @@ -94,21 +131,34 @@ public String obtainAuthorizationCode(OAuth2ProtectedResourceDetails details, Ac AuthorizationCodeResourceDetails resource = (AuthorizationCodeResourceDetails) details; - HttpHeaders headers = getHeadersForTokenRequest(request); + HttpHeaders headers = getHeadersForAuthorizationRequest(request); MultiValueMap form = new LinkedMultiValueMap(); - if (request.containsKey(AuthorizationRequest.USER_OAUTH_APPROVAL)) { - form.set(AuthorizationRequest.USER_OAUTH_APPROVAL, - request.getFirst(AuthorizationRequest.USER_OAUTH_APPROVAL)); + if (request.containsKey(OAuth2Utils.USER_OAUTH_APPROVAL)) { + form.set(OAuth2Utils.USER_OAUTH_APPROVAL, request.getFirst(OAuth2Utils.USER_OAUTH_APPROVAL)); + for (String scope : details.getScope()) { + form.set(scopePrefix + scope, request.getFirst(OAuth2Utils.USER_OAUTH_APPROVAL)); + } } else { form.putAll(getParametersForAuthorizeRequest(resource, request)); } + authorizationRequestEnhancer.enhance(request, resource, form, headers); + final AccessTokenRequest copy = request; + final ResponseExtractor> delegate = getAuthorizationResponseExtractor(); + ResponseExtractor> extractor = new ResponseExtractor>() { + @Override + public ResponseEntity extractData(ClientHttpResponse response) throws IOException { + if (response.getHeaders().containsKey("Set-Cookie")) { + copy.setCookie(response.getHeaders().getFirst("Set-Cookie")); + } + return delegate.extractData(response); + } + }; // Instead of using restTemplate.exchange we use an explicit response extractor here so it can be overridden by // subclasses ResponseEntity response = getRestTemplate().execute(resource.getUserAuthorizationUri(), HttpMethod.POST, - getRequestCallback(resource, form, headers), getAuthorizationResponseExtractor(), - form.toSingleValueMap()); + getRequestCallback(resource, form, headers), extractor, form.toSingleValueMap()); if (response.getStatusCode() == HttpStatus.OK) { // Need to re-submit with approval... @@ -160,8 +210,8 @@ public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails detail } obtainAuthorizationCode(resource, request); } - return retrieveToken(getParametersForTokenRequest(resource, request), getHeadersForTokenRequest(request), - resource); + return retrieveToken(request, resource, getParametersForTokenRequest(resource, request), + getHeadersForTokenRequest(request)); } @@ -169,10 +219,10 @@ public OAuth2AccessToken refreshAccessToken(OAuth2ProtectedResourceDetails resou OAuth2RefreshToken refreshToken, AccessTokenRequest request) throws UserRedirectRequiredException, OAuth2AccessDeniedException { MultiValueMap form = new LinkedMultiValueMap(); - form.add("grant_type", "refresh_token"); + form.add(OAuth2Utils.GRANT_TYPE, "refresh_token"); form.add("refresh_token", refreshToken.getValue()); try { - return retrieveToken(form, getHeadersForTokenRequest(request), resource); + return retrieveToken(request, resource, form, getHeadersForTokenRequest(request)); } catch (OAuth2AccessDeniedException e) { throw getRedirectForAuthorization((AuthorizationCodeResourceDetails) resource, request); @@ -181,6 +231,13 @@ public OAuth2AccessToken refreshAccessToken(OAuth2ProtectedResourceDetails resou private HttpHeaders getHeadersForTokenRequest(AccessTokenRequest request) { HttpHeaders headers = new HttpHeaders(); + // No cookie for token request + return headers; + } + + private HttpHeaders getHeadersForAuthorizationRequest(AccessTokenRequest request) { + HttpHeaders headers = new HttpHeaders(); + headers.putAll(request.getHeaders()); if (request.getCookie() != null) { headers.set("Cookie", request.getCookie()); } @@ -191,16 +248,16 @@ private MultiValueMap getParametersForTokenRequest(Authorization AccessTokenRequest request) { MultiValueMap form = new LinkedMultiValueMap(); - form.set("grant_type", "authorization_code"); + form.set(OAuth2Utils.GRANT_TYPE, "authorization_code"); form.set("code", request.getAuthorizationCode()); Object preservedState = request.getPreservedState(); - if (request.getStateKey() != null) { + if (request.getStateKey() != null || stateMandatory) { // The token endpoint has no use for the state so we don't send it back, but we are using it // for CSRF detection client side... if (preservedState == null) { throw new InvalidRequestException( - "Possible CSRF detected - state parameter was present but no state could be found"); + "Possible CSRF detected - state parameter was required but no state could be found"); } } @@ -212,11 +269,12 @@ private MultiValueMap getParametersForTokenRequest(Authorization // Use the preserved state in preference if it is there // TODO: treat redirect URI as a special kind of state (this is a historical mini hack) redirectUri = String.valueOf(preservedState); - } else { + } + else { redirectUri = resource.getRedirectUri(request); } - if (redirectUri != null) { + if (redirectUri != null && !"NONE".equals(redirectUri)) { form.set("redirect_uri", redirectUri); } @@ -279,10 +337,9 @@ private UserRedirectRequiredException getRedirectForAuthorization(AuthorizationC // Client secret is not required in the initial authorization request String redirectUri = resource.getRedirectUri(request); - if (redirectUri == null) { - throw new IllegalStateException("No redirect URI has been established for the current request."); + if (redirectUri != null) { + requestParameters.put("redirect_uri", redirectUri); } - requestParameters.put("redirect_uri", redirectUri); if (resource.isScoped()) { @@ -320,7 +377,7 @@ protected UserApprovalRequiredException getUserApprovalSignal(AuthorizationCodeR String message = String.format("Do you approve the client '%s' to access your resources with scope=%s", resource.getClientId(), resource.getScope()); return new UserApprovalRequiredException(resource.getUserAuthorizationUri(), Collections.singletonMap( - AuthorizationRequest.USER_OAUTH_APPROVAL, message), resource.getClientId(), resource.getScope()); + OAuth2Utils.USER_OAUTH_APPROVAL, message), resource.getClientId(), resource.getScope()); } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeResourceDetails.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeResourceDetails.java index 1e445c178..790b6b966 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeResourceDetails.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeResourceDetails.java @@ -3,9 +3,13 @@ import org.springframework.security.oauth2.client.token.grant.redirect.AbstractRedirectResourceDetails; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class AuthorizationCodeResourceDetails extends AbstractRedirectResourceDetails { public AuthorizationCodeResourceDetails() { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/implicit/ImplicitAccessTokenProvider.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/implicit/ImplicitAccessTokenProvider.java index dc3acb073..35b451ebe 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/implicit/ImplicitAccessTokenProvider.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/implicit/ImplicitAccessTokenProvider.java @@ -17,8 +17,8 @@ import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; import org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -34,9 +34,13 @@ * parameters, together with any other information available (e.g. from a cookie), and decide if a user can be * authenticated and if the user has approved the grant of the access token. Only if those two conditions are met should * an access token be available through this provider. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer */ +@Deprecated public class ImplicitAccessTokenProvider extends OAuth2AccessTokenSupport implements AccessTokenProvider { public boolean supportsResource(OAuth2ProtectedResourceDetails resource) { @@ -58,8 +62,8 @@ public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails detail ImplicitResourceDetails resource = (ImplicitResourceDetails) details; try { // We can assume here that the request contains all the parameters needed for authentication etc. - OAuth2AccessToken token = retrieveToken(getParametersForTokenRequest(resource, request), - getHeadersForTokenRequest(request), resource); + OAuth2AccessToken token = retrieveToken(request, + resource, getParametersForTokenRequest(resource, request), getHeadersForTokenRequest(request)); if (token==null) { // Probably an authenticated request, but approval is required. TODO: prompt somehow? throw new UserRedirectRequiredException(resource.getUserAuthorizationUri(), request.toSingleValueMap()); @@ -80,6 +84,7 @@ protected ResponseExtractor getResponseExtractor() { private HttpHeaders getHeadersForTokenRequest(AccessTokenRequest request) { HttpHeaders headers = new HttpHeaders(); + headers.putAll(request.getHeaders()); if (request.getCookie() != null) { headers.set("Cookie", request.getCookie()); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/implicit/ImplicitResourceDetails.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/implicit/ImplicitResourceDetails.java index 6acb5ba47..9593b63a7 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/implicit/ImplicitResourceDetails.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/implicit/ImplicitResourceDetails.java @@ -3,8 +3,12 @@ import org.springframework.security.oauth2.client.token.grant.redirect.AbstractRedirectResourceDetails; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer */ +@Deprecated public class ImplicitResourceDetails extends AbstractRedirectResourceDetails { public ImplicitResourceDetails() { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/password/ResourceOwnerPasswordAccessTokenProvider.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/password/ResourceOwnerPasswordAccessTokenProvider.java index e99051252..fb53594a7 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/password/ResourceOwnerPasswordAccessTokenProvider.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/password/ResourceOwnerPasswordAccessTokenProvider.java @@ -13,14 +13,19 @@ import org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; /** * Provider for obtaining an oauth2 access token by using resource owner password. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer */ +@Deprecated public class ResourceOwnerPasswordAccessTokenProvider extends OAuth2AccessTokenSupport implements AccessTokenProvider { public boolean supportsResource(OAuth2ProtectedResourceDetails resource) { @@ -35,26 +40,27 @@ public OAuth2AccessToken refreshAccessToken(OAuth2ProtectedResourceDetails resou OAuth2RefreshToken refreshToken, AccessTokenRequest request) throws UserRedirectRequiredException, OAuth2AccessDeniedException { MultiValueMap form = new LinkedMultiValueMap(); - form.add("grant_type", "refresh_token"); + form.add(OAuth2Utils.GRANT_TYPE, "refresh_token"); form.add("refresh_token", refreshToken.getValue()); - return retrieveToken(form, new HttpHeaders(), resource); + return retrieveToken(request, resource, form, new HttpHeaders()); } public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request) throws UserRedirectRequiredException, AccessDeniedException, OAuth2AccessDeniedException { ResourceOwnerPasswordResourceDetails resource = (ResourceOwnerPasswordResourceDetails) details; - return retrieveToken(getParametersForTokenRequest(resource), new HttpHeaders(), resource); + return retrieveToken(request, resource, getParametersForTokenRequest(resource, request), new HttpHeaders()); } - private MultiValueMap getParametersForTokenRequest(ResourceOwnerPasswordResourceDetails resource) { + private MultiValueMap getParametersForTokenRequest(ResourceOwnerPasswordResourceDetails resource, AccessTokenRequest request) { MultiValueMap form = new LinkedMultiValueMap(); - form.set("grant_type", "password"); + form.set(OAuth2Utils.GRANT_TYPE, "password"); form.set("username", resource.getUsername()); form.set("password", resource.getPassword()); + form.putAll(request); if (resource.isScoped()) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/password/ResourceOwnerPasswordResourceDetails.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/password/ResourceOwnerPasswordResourceDetails.java index b20155e8b..21ac5338f 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/password/ResourceOwnerPasswordResourceDetails.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/password/ResourceOwnerPasswordResourceDetails.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -18,8 +18,12 @@ import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer */ +@Deprecated public class ResourceOwnerPasswordResourceDetails extends BaseOAuth2ProtectedResourceDetails { private String username; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/redirect/AbstractRedirectResourceDetails.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/redirect/AbstractRedirectResourceDetails.java index 8b43a2cd4..15805dbf5 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/redirect/AbstractRedirectResourceDetails.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/grant/redirect/AbstractRedirectResourceDetails.java @@ -5,8 +5,12 @@ import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer */ +@Deprecated public abstract class AbstractRedirectResourceDetails extends BaseOAuth2ProtectedResourceDetails { private String preEstablishedRedirectUri; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/AuthenticationScheme.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/AuthenticationScheme.java index f4d75d22b..0ed5d6b67 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/AuthenticationScheme.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/AuthenticationScheme.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -14,7 +14,11 @@ /** * Enumeration of possible methods for transmitting authentication credentials. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. */ +@Deprecated public enum AuthenticationScheme { /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultExpiringOAuth2RefreshToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultExpiringOAuth2RefreshToken.java index 841480d52..4806e2a4c 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultExpiringOAuth2RefreshToken.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultExpiringOAuth2RefreshToken.java @@ -3,8 +3,12 @@ import java.util.Date; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@Deprecated public class DefaultExpiringOAuth2RefreshToken extends DefaultOAuth2RefreshToken implements ExpiringOAuth2RefreshToken { private static final long serialVersionUID = 3449554332764129719L; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultOAuth2AccessToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultOAuth2AccessToken.java index a20dc84ab..eeb5b6527 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultOAuth2AccessToken.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultOAuth2AccessToken.java @@ -11,11 +11,15 @@ /** * Basic access token for OAuth 2. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer * @author Rob Winch */ +@Deprecated public class DefaultOAuth2AccessToken implements Serializable, OAuth2AccessToken { private static final long serialVersionUID = 914967629530462926L; @@ -44,7 +48,7 @@ public DefaultOAuth2AccessToken(String value) { */ @SuppressWarnings("unused") private DefaultOAuth2AccessToken() { - this((String)null); + this((String) null); } /** @@ -60,11 +64,9 @@ public DefaultOAuth2AccessToken(OAuth2AccessToken accessToken) { setScope(accessToken.getScope()); setTokenType(accessToken.getTokenType()); } - - public DefaultOAuth2AccessToken setValue(String value) { - DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(this); - result.value = value; - return result; + + public void setValue(String value) { + this.value = value; } /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultOAuth2RefreshToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultOAuth2RefreshToken.java index c8df418d1..c78bb5410 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultOAuth2RefreshToken.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultOAuth2RefreshToken.java @@ -2,15 +2,19 @@ import java.io.Serializable; -import org.codehaus.jackson.annotate.JsonCreator; -import org.codehaus.jackson.annotate.JsonValue; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; /** * An OAuth 2 refresh token. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class DefaultOAuth2RefreshToken implements Serializable, OAuth2RefreshToken { private static final long serialVersionUID = 8349970621900575838L; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultThrowableAnalyzer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultThrowableAnalyzer.java index 72ba20a09..2f341c2f2 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultThrowableAnalyzer.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/DefaultThrowableAnalyzer.java @@ -8,7 +8,11 @@ /** * Default implementation of ThrowableAnalyzer which is capable of also unwrapping * ServletExceptions. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. */ +@Deprecated public final class DefaultThrowableAnalyzer extends ThrowableAnalyzer { /** * @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap() diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/ExpiringOAuth2RefreshToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/ExpiringOAuth2RefreshToken.java index abeb44b90..eb5fd46fe 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/ExpiringOAuth2RefreshToken.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/ExpiringOAuth2RefreshToken.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -15,9 +15,13 @@ import java.util.Date; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface ExpiringOAuth2RefreshToken extends OAuth2RefreshToken { Date getExpiration(); diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java index 0d2ea97ca..de4b0ada0 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -17,14 +17,15 @@ import java.util.Set; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ -@org.codehaus.jackson.map.annotate.JsonSerialize(using = OAuth2AccessTokenJackson1Serializer.class) -@org.codehaus.jackson.map.annotate.JsonDeserialize(using = OAuth2AccessTokenJackson1Deserializer.class) @com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2AccessTokenJackson2Serializer.class) @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class) - +@Deprecated public interface OAuth2AccessToken { public static String BEARER_TYPE = "Bearer"; @@ -38,7 +39,7 @@ public interface OAuth2AccessToken { /** * The type of the token issued as described in Section 7.1. Value is case insensitive. + * href="https://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-7.1">Section 7.1. Value is case insensitive. * This value is REQUIRED. */ public static String TOKEN_TYPE = "token_type"; @@ -51,13 +52,13 @@ public interface OAuth2AccessToken { /** * The refresh token which can be used to obtain new access tokens using the same authorization grant as described - * in Section 6. This value is OPTIONAL. + * in Section 6. This value is OPTIONAL. */ public static String REFRESH_TOKEN = "refresh_token"; /** * The scope of the access token as described by Section 3.3 + * href="https://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.3">Section 3.3 */ public static String SCOPE = "scope"; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson1Deserializer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson1Deserializer.java deleted file mode 100644 index 1aadd1eac..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson1Deserializer.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2006-2010 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package org.springframework.security.oauth2.common; - -import java.io.IOException; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonProcessingException; -import org.codehaus.jackson.JsonToken; -import org.codehaus.jackson.map.DeserializationContext; -import org.codehaus.jackson.map.JsonDeserializer; -import org.codehaus.jackson.map.deser.StdDeserializer; -import org.springframework.security.oauth2.common.util.OAuth2Utils; - -/** - *

- * Provides the ability to deserialize JSON response into an {@link OAuth2AccessToken} with jackson by implementing - * {@link JsonDeserializer}. - *

- *

- * The expected format of the access token is defined by Successful Response. - *

- * - * @author Rob Winch - * @see OAuth2AccessTokenJackson1Serializer - */ -@SuppressWarnings("deprecation") -public final class OAuth2AccessTokenJackson1Deserializer extends StdDeserializer { - - public OAuth2AccessTokenJackson1Deserializer() { - super(OAuth2AccessToken.class); - } - - @Override - public OAuth2AccessToken deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, - JsonProcessingException { - - String tokenValue = null; - String tokenType = null; - String refreshToken = null; - Long expiresIn = null; - Set scope = null; - Map additionalInformation = new LinkedHashMap(); - - // TODO What should occur if a parameter exists twice - while (jp.nextToken() != JsonToken.END_OBJECT) { - String name = jp.getCurrentName(); - jp.nextToken(); - if (OAuth2AccessToken.ACCESS_TOKEN.equals(name)) { - tokenValue = jp.getText(); - } - else if (OAuth2AccessToken.TOKEN_TYPE.equals(name)) { - tokenType = jp.getText(); - } - else if (OAuth2AccessToken.REFRESH_TOKEN.equals(name)) { - refreshToken = jp.getText(); - } - else if (OAuth2AccessToken.EXPIRES_IN.equals(name)) { - expiresIn = jp.getLongValue(); - } - else if (OAuth2AccessToken.SCOPE.equals(name)) { - String text = jp.getText(); - scope = OAuth2Utils.parseParameterList(text); - } else { - additionalInformation.put(name, jp.readValueAs(Object.class)); - } - } - - // TODO What should occur if a required parameter (tokenValue or tokenType) is missing? - - DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(tokenValue); - accessToken.setTokenType(tokenType); - if (expiresIn != null) { - accessToken.setExpiration(new Date(System.currentTimeMillis() + (expiresIn * 1000))); - } - if (refreshToken != null) { - accessToken.setRefreshToken(new DefaultOAuth2RefreshToken(refreshToken)); - } - accessToken.setScope(scope); - accessToken.setAdditionalInformation(additionalInformation); - - return accessToken; - } -} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson1Serializer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson1Serializer.java deleted file mode 100644 index 2f2768a32..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson1Serializer.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2006-2010 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package org.springframework.security.oauth2.common; - -import java.io.IOException; -import java.util.Date; -import java.util.Map; -import java.util.Set; - -import org.codehaus.jackson.JsonGenerationException; -import org.codehaus.jackson.JsonGenerator; -import org.codehaus.jackson.map.JsonSerializer; -import org.codehaus.jackson.map.SerializerProvider; -import org.codehaus.jackson.map.ser.SerializerBase; -import org.springframework.util.Assert; - -/** - * Provides the ability to serialize an {@link OAuth2AccessToken} with jackson by implementing {@link JsonSerializer}. - * Refer to {@link OAuth2AccessTokenJackson1Deserializer} to learn more about the JSON format that is used. - * - * @author Rob Winch - * @see OAuth2AccessTokenJackson1Deserializer - */ -@SuppressWarnings("deprecation") -public final class OAuth2AccessTokenJackson1Serializer extends SerializerBase { - - public OAuth2AccessTokenJackson1Serializer() { - super(OAuth2AccessToken.class); - } - - @Override - public void serialize(OAuth2AccessToken token, JsonGenerator jgen, SerializerProvider provider) throws IOException, - JsonGenerationException { - jgen.writeStartObject(); - jgen.writeStringField(OAuth2AccessToken.ACCESS_TOKEN, token.getValue()); - jgen.writeStringField(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType()); - OAuth2RefreshToken refreshToken = token.getRefreshToken(); - if (refreshToken != null) { - jgen.writeStringField(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue()); - } - Date expiration = token.getExpiration(); - if (expiration != null) { - long now = System.currentTimeMillis(); - jgen.writeNumberField(OAuth2AccessToken.EXPIRES_IN, (expiration.getTime() - now) / 1000); - } - Set scope = token.getScope(); - if (scope != null && !scope.isEmpty()) { - StringBuffer scopes = new StringBuffer(); - for (String s : scope) { - Assert.hasLength(s, "Scopes cannot be null or empty. Got " + scope + ""); - scopes.append(s); - scopes.append(" "); - } - jgen.writeStringField(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length() - 1)); - } - Map additionalInformation = token.getAdditionalInformation(); - for (String key : additionalInformation.keySet()) { - jgen.writeObjectField(key, additionalInformation.get(key)); - } - jgen.writeEndObject(); - } -} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2Deserializer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2Deserializer.java index 73df07f39..ff0084019 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2Deserializer.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2Deserializer.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -13,18 +13,21 @@ package org.springframework.security.oauth2.common; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import org.springframework.security.oauth2.common.util.OAuth2Utils; - import java.io.IOException; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import java.util.TreeSet; + +import org.springframework.security.oauth2.common.util.OAuth2Utils; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; /** *

@@ -33,14 +36,18 @@ *

*

* The expected format of the access token is defined by Successful Response. + * href="https://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-5.1">Successful Response. *

* + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Rob Winch * @author Brian Clozel * @see org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Serializer */ -@SuppressWarnings("deprecation") +@SuppressWarnings("serial") +@Deprecated public final class OAuth2AccessTokenJackson2Deserializer extends StdDeserializer { public OAuth2AccessTokenJackson2Deserializer() { @@ -72,11 +79,14 @@ else if (OAuth2AccessToken.REFRESH_TOKEN.equals(name)) { refreshToken = jp.getText(); } else if (OAuth2AccessToken.EXPIRES_IN.equals(name)) { - expiresIn = jp.getLongValue(); + try { + expiresIn = jp.getLongValue(); + } catch (JsonParseException e) { + expiresIn = Long.valueOf(jp.getText()); + } } else if (OAuth2AccessToken.SCOPE.equals(name)) { - String text = jp.getText(); - scope = OAuth2Utils.parseParameterList(text); + scope = parseScope(jp); } else { additionalInformation.put(name, jp.readValueAs(Object.class)); } @@ -86,7 +96,7 @@ else if (OAuth2AccessToken.SCOPE.equals(name)) { DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(tokenValue); accessToken.setTokenType(tokenType); - if (expiresIn != null) { + if (expiresIn != null && expiresIn != 0) { accessToken.setExpiration(new Date(System.currentTimeMillis() + (expiresIn * 1000))); } if (refreshToken != null) { @@ -97,4 +107,18 @@ else if (OAuth2AccessToken.SCOPE.equals(name)) { return accessToken; } + + private Set parseScope(JsonParser jp) throws JsonParseException, IOException { + Set scope; + if (jp.getCurrentToken() == JsonToken.START_ARRAY) { + scope = new TreeSet(); + while (jp.nextToken() != JsonToken.END_ARRAY) { + scope.add(jp.getValueAsString()); + } + } else { + String text = jp.getText(); + scope = OAuth2Utils.parseParameterList(text); + } + return scope; + } } \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2Serializer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2Serializer.java index cdbe86277..01fcb0921 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2Serializer.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2Serializer.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -12,26 +12,31 @@ */ package org.springframework.security.oauth2.common; -import com.fasterxml.jackson.core.JsonGenerationException; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import org.springframework.util.Assert; - import java.io.IOException; import java.util.Date; import java.util.Map; import java.util.Set; +import org.springframework.util.Assert; + +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + /** * Provides the ability to serialize an {@link org.springframework.security.oauth2.common.OAuth2AccessToken} with jackson2 by implementing {@link com.fasterxml.jackson.databind.JsonDeserializer}. - * Refer to {@link org.springframework.security.oauth2.common.OAuth2AccessTokenJackson1Deserializer} to learn more about the JSON format that is used. + * + * The expected format of the access token is defined by Successful Response. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. * * @author Rob Winch * @author Brian Clozel * @see org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Deserializer */ -@SuppressWarnings("deprecation") +@Deprecated public final class OAuth2AccessTokenJackson2Serializer extends StdSerializer { public OAuth2AccessTokenJackson2Serializer() { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2RefreshToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2RefreshToken.java index 9283b5e9a..495fda30d 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2RefreshToken.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2RefreshToken.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -12,12 +12,16 @@ */ package org.springframework.security.oauth2.common; -import org.codehaus.jackson.annotate.JsonValue; +import com.fasterxml.jackson.annotation.JsonValue; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface OAuth2RefreshToken { /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/BadClientCredentialsException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/BadClientCredentialsException.java index 4f523f046..522af2175 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/BadClientCredentialsException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/BadClientCredentialsException.java @@ -3,9 +3,14 @@ /** * Exception thrown when a client was unable to authenticate. * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@SuppressWarnings("serial") +@Deprecated public class BadClientCredentialsException extends ClientAuthenticationException { public BadClientCredentialsException() { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/ClientAuthenticationException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/ClientAuthenticationException.java index 213e7115a..42d8dfe24 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/ClientAuthenticationException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/ClientAuthenticationException.java @@ -2,10 +2,15 @@ /** * Base exception - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@SuppressWarnings("serial") +@Deprecated public abstract class ClientAuthenticationException extends OAuth2Exception { public ClientAuthenticationException(String msg, Throwable t) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InsufficientScopeException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InsufficientScopeException.java index 1ffebee44..ff80461a2 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InsufficientScopeException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InsufficientScopeException.java @@ -8,9 +8,14 @@ /** * Exception representing insufficient scope in a token when a request is handled by a Resource Server. It is akin to an * {@link AccessDeniedException} and should result in a 403 (FORBIDDEN) HTTP status. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer */ +@SuppressWarnings("serial") +@Deprecated public class InsufficientScopeException extends OAuth2Exception { public InsufficientScopeException(String msg, Set validScope) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidClientException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidClientException.java index f279ecbcc..6aefe7d37 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidClientException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidClientException.java @@ -3,9 +3,14 @@ /** * Exception thrown when a client was unable to authenticate. * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@SuppressWarnings("serial") +@Deprecated public class InvalidClientException extends ClientAuthenticationException { public InvalidClientException(String msg) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidGrantException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidGrantException.java index df17701dc..2294cdffa 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidGrantException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidGrantException.java @@ -1,9 +1,14 @@ package org.springframework.security.oauth2.common.exceptions; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@SuppressWarnings("serial") +@Deprecated public class InvalidGrantException extends ClientAuthenticationException { public InvalidGrantException(String msg, Throwable t) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidRequestException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidRequestException.java index f02cce337..7b18897f4 100755 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidRequestException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidRequestException.java @@ -1,8 +1,13 @@ package org.springframework.security.oauth2.common.exceptions; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer */ +@SuppressWarnings("serial") +@Deprecated public class InvalidRequestException extends ClientAuthenticationException { public InvalidRequestException(String msg, Throwable t) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidScopeException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidScopeException.java index 54daa9d7e..4a8f4b3b1 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidScopeException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidScopeException.java @@ -8,10 +8,15 @@ * Exception representing an invalid scope in a token or authorization request (i.e. from an Authorization Server). Note * that this is not the same as an access denied exception if the scope presented to a Resource Server is insufficient. * The spec in this case mandates a 400 status code. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@SuppressWarnings("serial") +@Deprecated public class InvalidScopeException extends OAuth2Exception { public InvalidScopeException(String msg, Set validScope) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidTokenException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidTokenException.java index cab3fd00b..e8cb37507 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidTokenException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/InvalidTokenException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,9 +16,14 @@ package org.springframework.security.oauth2.common.exceptions; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@SuppressWarnings("serial") +@Deprecated public class InvalidTokenException extends ClientAuthenticationException { public InvalidTokenException(String msg, Throwable t) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2Exception.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2Exception.java index 265b6e414..4b4e9a802 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2Exception.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2Exception.java @@ -1,25 +1,23 @@ package org.springframework.security.oauth2.common.exceptions; -import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson1Deserializer; -import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson1Serializer; -import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Deserializer; -import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Serializer; - import java.util.Map; import java.util.Set; import java.util.TreeMap; /** * Base exception for OAuth 2 exceptions. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Rob Winch * @author Dave Syer */ -@org.codehaus.jackson.map.annotate.JsonSerialize(using = OAuth2ExceptionJackson1Serializer.class) -@org.codehaus.jackson.map.annotate.JsonDeserialize(using = OAuth2ExceptionJackson1Deserializer.class) +@SuppressWarnings("serial") @com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2ExceptionJackson2Serializer.class) @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2ExceptionJackson2Deserializer.class) +@Deprecated public class OAuth2Exception extends RuntimeException { public static final String ERROR = "error"; @@ -31,6 +29,7 @@ public class OAuth2Exception extends RuntimeException { public static final String UNAUTHORIZED_CLIENT = "unauthorized_client"; public static final String UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type"; public static final String INVALID_SCOPE = "invalid_scope"; + public static final String INSUFFICIENT_SCOPE = "insufficient_scope"; public static final String INVALID_TOKEN = "invalid_token"; public static final String REDIRECT_URI_MISMATCH ="redirect_uri_mismatch"; public static final String UNSUPPORTED_RESPONSE_TYPE ="unsupported_response_type"; @@ -134,7 +133,7 @@ else if (ACCESS_DENIED.equals(errorCode)) { } /** - * Creates an {@link OAuth2Exception} from a Map. + * Creates an {@link OAuth2Exception} from a Map<String,String>. * * @param errorParams * @return diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson1Deserializer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson1Deserializer.java deleted file mode 100644 index 52221d953..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson1Deserializer.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2006-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package org.springframework.security.oauth2.common.exceptions; - -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonProcessingException; -import org.codehaus.jackson.JsonToken; -import org.codehaus.jackson.map.DeserializationContext; -import org.codehaus.jackson.map.JsonDeserializer; - -/** - * @author Dave Syer - * - */ -public class OAuth2ExceptionJackson1Deserializer extends JsonDeserializer { - - @Override - public OAuth2Exception deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, - JsonProcessingException { - - JsonToken t = jp.getCurrentToken(); - if (t == JsonToken.START_OBJECT) { - t = jp.nextToken(); - } - Map errorParams = new HashMap(); - for (; t == JsonToken.FIELD_NAME; t = jp.nextToken()) { - // Must point to field name - String fieldName = jp.getCurrentName(); - // And then the value... - t = jp.nextToken(); - // Note: must handle null explicitly here; value deserializers won't - Object value; - if (t == JsonToken.VALUE_NULL) { - value = null; - } - // Some servers might send back complex content - else if (t == JsonToken.START_ARRAY) { - value = jp.readValueAs(List.class); - } - else if (t == JsonToken.START_OBJECT) { - value = jp.readValueAs(Map.class); - } - else { - value = jp.getText(); - } - errorParams.put(fieldName, value); - } - - Object errorCode = errorParams.get("error"); - String errorMessage = errorParams.containsKey("error_description") ? errorParams.get("error_description") - .toString() : null; - if (errorMessage == null) { - errorMessage = errorCode == null ? "OAuth Error" : errorCode.toString(); - } - - OAuth2Exception ex; - if ("invalid_client".equals(errorCode)) { - ex = new InvalidClientException(errorMessage); - } - else if ("unauthorized_client".equals(errorCode)) { - ex = new UnauthorizedClientException(errorMessage); - } - else if ("invalid_grant".equals(errorCode)) { - ex = new InvalidGrantException(errorMessage); - } - else if ("invalid_scope".equals(errorCode)) { - ex = new InvalidScopeException(errorMessage); - } - else if ("invalid_token".equals(errorCode)) { - ex = new InvalidTokenException(errorMessage); - } - else if ("invalid_request".equals(errorCode)) { - ex = new InvalidRequestException(errorMessage); - } - else if ("redirect_uri_mismatch".equals(errorCode)) { - ex = new RedirectMismatchException(errorMessage); - } - else if ("unsupported_grant_type".equals(errorCode)) { - ex = new UnsupportedGrantTypeException(errorMessage); - } - else if ("unsupported_response_type".equals(errorCode)) { - ex = new UnsupportedResponseTypeException(errorMessage); - } - else if ("access_denied".equals(errorCode)) { - ex = new UserDeniedAuthorizationException(errorMessage); - } - else { - ex = new OAuth2Exception(errorMessage); - } - - Set> entries = errorParams.entrySet(); - for (Map.Entry entry : entries) { - String key = entry.getKey(); - if (!"error".equals(key) && !"error_description".equals(key)) { - Object value = entry.getValue(); - ex.addAdditionalInformation(key, value == null ? null : value.toString()); - } - } - - return ex; - - } - -} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson1Serializer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson1Serializer.java deleted file mode 100644 index bfc33157f..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson1Serializer.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2006-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package org.springframework.security.oauth2.common.exceptions; - -import java.io.IOException; -import java.util.Map.Entry; - -import org.codehaus.jackson.JsonGenerator; -import org.codehaus.jackson.JsonProcessingException; -import org.codehaus.jackson.map.JsonSerializer; -import org.codehaus.jackson.map.SerializerProvider; - -/** - * @author Dave Syer - * - */ -public class OAuth2ExceptionJackson1Serializer extends JsonSerializer { - - @Override - public void serialize(OAuth2Exception value, JsonGenerator jgen, SerializerProvider provider) throws IOException, - JsonProcessingException { - jgen.writeStartObject(); - jgen.writeStringField("error", value.getOAuth2ErrorCode()); - jgen.writeStringField("error_description", value.getMessage()); - if (value.getAdditionalInformation()!=null) { - for (Entry entry : value.getAdditionalInformation().entrySet()) { - String key = entry.getKey(); - String add = entry.getValue(); - jgen.writeStringField(key, add); - } - } - jgen.writeEndObject(); - } - -} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson2Deserializer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson2Deserializer.java index 8e42b1d93..0cd8dbba1 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson2Deserializer.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson2Deserializer.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -12,7 +12,6 @@ */ package org.springframework.security.oauth2.common.exceptions; - import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; @@ -25,15 +24,22 @@ import java.util.Map; import java.util.Set; +import org.springframework.security.oauth2.common.util.OAuth2Utils; + /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Brian Clozel * */ +@SuppressWarnings("serial") +@Deprecated public class OAuth2ExceptionJackson2Deserializer extends StdDeserializer { - public OAuth2ExceptionJackson2Deserializer() { - super(OAuth2Exception.class); - } + public OAuth2ExceptionJackson2Deserializer() { + super(OAuth2Exception.class); + } @Override public OAuth2Exception deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, @@ -68,8 +74,7 @@ else if (t == JsonToken.START_OBJECT) { } Object errorCode = errorParams.get("error"); - String errorMessage = errorParams.containsKey("error_description") ? errorParams.get("error_description") - .toString() : null; + String errorMessage = errorParams.get("error_description") != null ? errorParams.get("error_description").toString() : null; if (errorMessage == null) { errorMessage = errorCode == null ? "OAuth Error" : errorCode.toString(); } @@ -82,7 +87,12 @@ else if ("unauthorized_client".equals(errorCode)) { ex = new UnauthorizedClientException(errorMessage); } else if ("invalid_grant".equals(errorCode)) { - ex = new InvalidGrantException(errorMessage); + if (errorMessage.toLowerCase().contains("redirect") && errorMessage.toLowerCase().contains("match")) { + ex = new RedirectMismatchException(errorMessage); + } + else { + ex = new InvalidGrantException(errorMessage); + } } else if ("invalid_scope".equals(errorCode)) { ex = new InvalidScopeException(errorMessage); @@ -102,6 +112,10 @@ else if ("unsupported_grant_type".equals(errorCode)) { else if ("unsupported_response_type".equals(errorCode)) { ex = new UnsupportedResponseTypeException(errorMessage); } + else if ("insufficient_scope".equals(errorCode)) { + ex = new InsufficientScopeException(errorMessage, OAuth2Utils.parseParameterList((String) errorParams + .get("scope"))); + } else if ("access_denied".equals(errorCode)) { ex = new UserDeniedAuthorizationException(errorMessage); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson2Serializer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson2Serializer.java index 161b2e387..0ed45881d 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson2Serializer.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson2Serializer.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -12,19 +12,22 @@ */ package org.springframework.security.oauth2.common.exceptions; -import com.fasterxml.jackson.core.JsonGenerationException; +import java.io.IOException; +import java.util.Map.Entry; + import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import com.fasterxml.jackson.core.JsonProcessingException; - -import java.io.IOException; -import java.util.Map.Entry; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Brian Clozel * */ +@Deprecated public class OAuth2ExceptionJackson2Serializer extends StdSerializer { public OAuth2ExceptionJackson2Serializer() { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/RedirectMismatchException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/RedirectMismatchException.java index eff5bb97b..989980291 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/RedirectMismatchException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/RedirectMismatchException.java @@ -1,8 +1,13 @@ package org.springframework.security.oauth2.common.exceptions; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class RedirectMismatchException extends ClientAuthenticationException { public RedirectMismatchException(String msg, Throwable t) { @@ -15,6 +20,6 @@ public RedirectMismatchException(String msg) { @Override public String getOAuth2ErrorCode() { - return "redirect_uri_mismatch"; + return "invalid_grant"; } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/SerializationException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/SerializationException.java index 177f0e252..14d41a53f 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/SerializationException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/SerializationException.java @@ -3,8 +3,13 @@ /** * Thrown during a problem serialization/deserialization. * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class SerializationException extends RuntimeException { public SerializationException() { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnapprovedClientAuthenticationException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnapprovedClientAuthenticationException.java index b04f556e4..1a787bffd 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnapprovedClientAuthenticationException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnapprovedClientAuthenticationException.java @@ -3,8 +3,13 @@ import org.springframework.security.authentication.InsufficientAuthenticationException; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class UnapprovedClientAuthenticationException extends InsufficientAuthenticationException { public UnapprovedClientAuthenticationException(String msg) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnauthorizedClientException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnauthorizedClientException.java index cf8b0d13f..595fe52eb 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnauthorizedClientException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnauthorizedClientException.java @@ -2,9 +2,14 @@ /** * Exception thrown when a client was unable to authenticate. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class UnauthorizedClientException extends ClientAuthenticationException { public UnauthorizedClientException(String msg, Throwable t) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnauthorizedUserException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnauthorizedUserException.java new file mode 100644 index 000000000..ba0ebf466 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnauthorizedUserException.java @@ -0,0 +1,34 @@ +package org.springframework.security.oauth2.common.exceptions; + +/** + * Exception thrown when a user was unable to authenticate. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + */ +@SuppressWarnings("serial") +@Deprecated +public class UnauthorizedUserException extends OAuth2Exception { + + public UnauthorizedUserException(String msg, Throwable t) { + super(msg, t); + } + + public UnauthorizedUserException(String msg) { + super(msg); + } + + @Override + public int getHttpErrorCode() { + // The spec says this can be unauthorized + return 401; + } + + @Override + public String getOAuth2ErrorCode() { + // Not in the spec + return "unauthorized_user"; + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnsupportedGrantTypeException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnsupportedGrantTypeException.java index a2573989d..4fba4ff0a 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnsupportedGrantTypeException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnsupportedGrantTypeException.java @@ -1,8 +1,13 @@ package org.springframework.security.oauth2.common.exceptions; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class UnsupportedGrantTypeException extends OAuth2Exception { public UnsupportedGrantTypeException(String msg, Throwable t) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnsupportedResponseTypeException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnsupportedResponseTypeException.java index d09e444fa..588600f88 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnsupportedResponseTypeException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UnsupportedResponseTypeException.java @@ -1,8 +1,13 @@ package org.springframework.security.oauth2.common.exceptions; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class UnsupportedResponseTypeException extends OAuth2Exception { public UnsupportedResponseTypeException(String msg, Throwable t) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UserDeniedAuthorizationException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UserDeniedAuthorizationException.java index 2718f99ef..94715b6e7 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UserDeniedAuthorizationException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/UserDeniedAuthorizationException.java @@ -1,8 +1,13 @@ package org.springframework.security.oauth2.common.exceptions; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@SuppressWarnings("serial") +@Deprecated public class UserDeniedAuthorizationException extends OAuth2Exception { public UserDeniedAuthorizationException(String msg, Throwable t) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/DefaultJdbcListFactory.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/DefaultJdbcListFactory.java index e05362f77..61a397fbc 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/DefaultJdbcListFactory.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/DefaultJdbcListFactory.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -23,9 +23,13 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class DefaultJdbcListFactory implements JdbcListFactory { private final NamedParameterJdbcOperations jdbcTemplate; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/DefaultSerializationStrategy.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/DefaultSerializationStrategy.java new file mode 100644 index 000000000..f8ade4824 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/DefaultSerializationStrategy.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-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.common.util; + +import org.springframework.core.ConfigurableObjectInputStream; + +import java.io.*; + +/** + * The default {@link SerializationStrategy} which uses the built-in Java serialization mechanism. + *

+ * Note that this class should not be used if data for deserialization comes from an untrusted source. + * Instead, please use {@link WhitelistedSerializationStrategy} with a list of allowed classes for deserialization. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Artem Smotrakov + * @since 2.4 + */ +@Deprecated +public class DefaultSerializationStrategy implements SerializationStrategy { + + public byte[] serialize(Object state) { + ObjectOutputStream oos = null; + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(512); + oos = new ObjectOutputStream(bos); + oos.writeObject(state); + oos.flush(); + return bos.toByteArray(); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } finally { + if (oos != null) { + try { + oos.close(); + } catch (IOException e) { + // eat it + } + } + } + } + + public T deserialize(byte[] byteArray) { + ObjectInputStream oip = null; + try { + oip = createObjectInputStream(byteArray); + @SuppressWarnings("unchecked") + T result = (T) oip.readObject(); + return result; + } catch (IOException e) { + throw new IllegalArgumentException(e); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(e); + } finally { + if (oip != null) { + try { + oip.close(); + } catch (IOException e) { + // eat it + } + } + } + } + + /** + * Creates an {@link ObjectInputStream} for deserialization. + * + * @param byteArray Data to be deserialized. + * @return An instance of {@link ObjectInputStream} which should be used for deserialization. + * @throws IOException If something went wrong. + */ + protected ObjectInputStream createObjectInputStream(byte[] byteArray) throws IOException { + return new ConfigurableObjectInputStream(new ByteArrayInputStream(byteArray), + Thread.currentThread().getContextClassLoader()); + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/Jackson2JsonParser.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/Jackson2JsonParser.java new file mode 100644 index 000000000..6537d0304 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/Jackson2JsonParser.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013-2014 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.common.util; + +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; + + + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class Jackson2JsonParser implements JsonParser { + + private ObjectMapper mapper = new ObjectMapper(); + + @SuppressWarnings("unchecked") + @Override + public Map parseMap(String json) { + try { + return mapper.readValue(json, Map.class); + } + catch (Exception e) { + throw new IllegalArgumentException("Cannot parse json", e); + } + } + + @Override + public String formatMap(Map map) { + try { + return mapper.writeValueAsString(map); + } + catch (Exception e) { + throw new IllegalArgumentException("Cannot format json", e); + } + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JdbcListFactory.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JdbcListFactory.java index 1a58e4ef9..9c5b11cfe 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JdbcListFactory.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JdbcListFactory.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -22,9 +22,13 @@ import org.springframework.jdbc.core.RowMapper; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface JdbcListFactory { /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JsonDateDeserializer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JsonDateDeserializer.java new file mode 100644 index 000000000..a2b1a9705 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JsonDateDeserializer.java @@ -0,0 +1,54 @@ +/* + * 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.common.util; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +/** + * JSON deserializer for Jackson to handle regular date instances as timestamps in ISO format. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class JsonDateDeserializer extends JsonDeserializer { + + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + + @Override + public Date deserialize(com.fasterxml.jackson.core.JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException { + try { + synchronized (dateFormat) { + return dateFormat.parse(parser.getText()); + } + } + catch (ParseException e) { + throw new JsonParseException("Could not parse date", parser.getCurrentLocation(), e); + } + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JsonDateSerializer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JsonDateSerializer.java new file mode 100644 index 000000000..11fea8daf --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JsonDateSerializer.java @@ -0,0 +1,51 @@ +/* + * 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.common.util; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +/** + * JSON serializer for Jackson to handle regular date instances as timestamps in ISO format. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class JsonDateSerializer extends JsonSerializer { + + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + + @Override + public void serialize(Date date, JsonGenerator generator, SerializerProvider provider) throws IOException, + JsonProcessingException { + synchronized (dateFormat) { + String formatted = dateFormat.format(date); + generator.writeString(formatted); + } + } + +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JsonParser.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JsonParser.java new file mode 100644 index 000000000..be1e0cc58 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JsonParser.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013-2014 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.common.util; + +import java.util.Map; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public interface JsonParser { + + /** + * Parse the specified JSON string into a Map. + * @param json the JSON to parse + * @return the parsed JSON as a map + */ + Map parseMap(String json); + + /** + * Convert the Map to JSON + * @param map a map to format + * @return a JSON representation of the map + */ + String formatMap(Map map); + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JsonParserFactory.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JsonParserFactory.java new file mode 100644 index 000000000..aeadcbd7d --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/JsonParserFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2014 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.common.util; + +import org.springframework.util.ClassUtils; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class JsonParserFactory { + + public static JsonParser create() { + if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", null)) { + return new Jackson2JsonParser(); + } + throw new IllegalStateException("No Jackson 2 parser found. Please add Jackson 2 to your classpath."); + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/OAuth2Utils.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/OAuth2Utils.java index a5ab3fc19..ba9988362 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/OAuth2Utils.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/OAuth2Utils.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -26,11 +27,55 @@ import org.springframework.util.StringUtils; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public abstract class OAuth2Utils { + /** + * Constant to use while parsing and formatting parameter maps for OAuth2 requests + */ + public static final String CLIENT_ID = "client_id"; + + /** + * Constant to use while parsing and formatting parameter maps for OAuth2 requests + */ + public static final String STATE = "state"; + + /** + * Constant to use while parsing and formatting parameter maps for OAuth2 requests + */ + public static final String SCOPE = "scope"; + + /** + * Constant to use while parsing and formatting parameter maps for OAuth2 requests + */ + public static final String REDIRECT_URI = "redirect_uri"; + + /** + * Constant to use while parsing and formatting parameter maps for OAuth2 requests + */ + public static final String RESPONSE_TYPE = "response_type"; + + /** + * Constant to use while parsing and formatting parameter maps for OAuth2 requests + */ + public static final String USER_OAUTH_APPROVAL = "user_oauth_approval"; + + /** + * Constant to use as a prefix for scope approval + */ + public static final String SCOPE_PREFIX = "scope."; + + /** + * Constant to use while parsing and formatting parameter maps for OAuth2 requests + */ + public static final String GRANT_TYPE = "grant_type"; + /** * Parses a string parameter value into a set of strings. * @@ -40,8 +85,8 @@ public abstract class OAuth2Utils { public static Set parseParameterList(String values) { Set result = new TreeSet(); if (values != null && values.trim().length() > 0) { - // the spec says the scope is separated by spaces, but Facebook uses commas, so we'll include commas, too. - String[] tokens = values.split("[\\s+,]"); + // the spec says the scope is separated by spaces + String[] tokens = values.split("[\\s+]"); result.addAll(Arrays.asList(tokens)); } return result; @@ -74,4 +119,17 @@ public static Map extractMap(String query) { } return map; } + + /** + * Compare 2 sets and check that one contains all members of the other. + * + * @param target set of strings to check + * @param members the members to compare to + * @return true if all members are in the target + */ + public static boolean containsAll(Set target, Set members) { + target = new HashSet(target); + target.retainAll(members); + return target.size() == members.size(); + } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/ProxyCreator.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/ProxyCreator.java new file mode 100644 index 000000000..c3562bf52 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/ProxyCreator.java @@ -0,0 +1,75 @@ +/* + * Copyright 2013-2014 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.common.util; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.springframework.beans.factory.ObjectFactory; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class ProxyCreator { + + @SuppressWarnings("unchecked") + public static T getProxy(Class type, ObjectFactory factory) { + return (T) Proxy.newProxyInstance(ProxyCreator.class.getClassLoader(), new Class[] { type }, + new LazyInvocationHandler(factory)); + } + + private static class LazyInvocationHandler implements InvocationHandler { + + private T target; + + private ObjectFactory factory; + + public LazyInvocationHandler(ObjectFactory factory) { + this.factory = factory; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // Invocation on interface coming in... + + if (method.getName().equals("equals")) { + return (proxy == args[0]); + } + else if (method.getName().equals("hashCode")) { + return System.identityHashCode(proxy); + } + try { + return method.invoke(getTarget(method), args); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + + private Object getTarget(Method method) { + if (target == null) { + target = factory.getObject(); + } + return target; + } + + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/RandomValueStringGenerator.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/RandomValueStringGenerator.java index 6dd551d42..33a3791ae 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/RandomValueStringGenerator.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/RandomValueStringGenerator.java @@ -1,3 +1,18 @@ +/* + * Copyright 2012-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.common.util; import java.security.SecureRandom; @@ -5,13 +20,17 @@ /** * Utility that generates a random-value ASCII string. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class RandomValueStringGenerator { - private static final char[] DEFAULT_CODEC = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + private static final char[] DEFAULT_CODEC = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_" .toCharArray(); private Random random = new SecureRandom(); @@ -43,7 +62,7 @@ public String generate() { /** * Convert these random bytes to a verifier string. The length of the byte array can be * {@link #setLength(int) configured}. The default implementation mods the bytes to fit into the - * ASCII letters 1-9, A-Z, a-z . + * ASCII letters 1-9, A-Z, a-z, -_ . * * @param verifierBytes The bytes. * @return The string. @@ -66,11 +85,14 @@ public void setRandom(Random random) { } /** - * The length of string to generate. + * The length of string to generate. A length less than or equal to 0 will result in an {@code IllegalArgumentException}. * * @param length the length to set */ public void setLength(int length) { + if (length <= 0) { + throw new IllegalArgumentException("length must be greater than 0"); + } this.length = length; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/SerializationStrategy.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/SerializationStrategy.java new file mode 100644 index 000000000..72f9f6060 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/SerializationStrategy.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-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.common.util; + +/** + * Defines how objects are serialized and deserialized. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Artem Smotrakov + * @since 2.4 + */ +@Deprecated +public interface SerializationStrategy { + + /** + * Serializes an object. + * + * @param object The object to be serialized. + * @return A byte array. + */ + byte[] serialize(Object object); + + /** + * Deserializes an object from a byte array. + * + * @param byteArray The byte array. + * @param The type of the object. + * @return The deserialized object. + */ + T deserialize(byte[] byteArray); + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/SerializationUtils.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/SerializationUtils.java index 62e20f7c5..fe554e88e 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/SerializationUtils.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/SerializationUtils.java @@ -1,57 +1,80 @@ +/* + * Copyright 2012-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.common.util; -import java.io.*; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.util.Assert; + +import java.util.List; +/** + * This is a helper class for serializing and deserializing objects with a {@link SerializationStrategy}. + * The class looks for the strategy in {@code META-INF/spring.factories}, + * or the strategy can also be set by calling {@link #setSerializationStrategy(SerializationStrategy)}. + * If no strategy is specified, the default is {@link DefaultSerializationStrategy}. + *

+ * Note that the default strategy allows deserializing arbitrary classes which may result in security problems + * if data comes from an untrusted source. To prevent possible issues, use {@link WhitelistedSerializationStrategy} + * with a list of allowed classes for deserialization. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + */ +@Deprecated public class SerializationUtils { - public static byte[] serialize(Object state) { - ObjectOutputStream oos = null; - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(512); - oos = new ObjectOutputStream(bos); - oos.writeObject(state); - oos.flush(); - return bos.toByteArray(); - } - catch (IOException e) { - throw new IllegalArgumentException(e); - } - finally { - if (oos != null) { - try { - oos.close(); - } - catch (IOException e) { - // eat it - } - } - } - } - - public static T deserialize(byte[] byteArray) { - ObjectInputStream oip = null; - try { - oip = new ObjectInputStream(new ByteArrayInputStream(byteArray)); - @SuppressWarnings("unchecked") - T result = (T) oip.readObject(); - return result; - } - catch (IOException e) { - throw new IllegalArgumentException(e); - } - catch (ClassNotFoundException e) { - throw new IllegalArgumentException(e); - } - finally { - if (oip != null) { - try { - oip.close(); - } - catch (IOException e) { - // eat it - } - } - } - } - -} + private static SerializationStrategy strategy = new DefaultSerializationStrategy(); + + static { + List strategies = SpringFactoriesLoader.loadFactories( + SerializationStrategy.class, SerializationUtils.class.getClassLoader()); + if (strategies.size() > 1) { + throw new IllegalArgumentException( + "Too many serialization strategies in META-INF/spring.factories"); + } + if (strategies.size() == 1) { + strategy = strategies.get(0); + } + } + + /** + * @return The current serialization strategy. + */ + public static SerializationStrategy getSerializationStrategy() { + return strategy; + } + + /** + * Sets a new serialization strategy. + * + * @param serializationStrategy The serialization strategy. + */ + public static void setSerializationStrategy(SerializationStrategy serializationStrategy) { + Assert.notNull(serializationStrategy, "serializationStrategy cannot be null"); + strategy = serializationStrategy; + } + + public static byte[] serialize(Object object) { + return strategy.serialize(object); + } + + public static T deserialize(byte[] byteArray) { + return strategy.deserialize(byteArray); + } + +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/WhitelistedSerializationStrategy.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/WhitelistedSerializationStrategy.java new file mode 100644 index 000000000..f4a32ba3b --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/util/WhitelistedSerializationStrategy.java @@ -0,0 +1,147 @@ +/* + * Copyright 2012-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.common.util; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.NotSerializableException; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.util.ClassUtils; + +/** + * A {@link SerializationStrategy} which uses a whitelist of allowed classes for deserialization. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Artem Smotrakov + * @since 2.4 + */ +@Deprecated +public class WhitelistedSerializationStrategy extends DefaultSerializationStrategy { + + /** + * A list of classes which are allowed to deserialize by default. + */ + private static final List DEFAULT_ALLOWED_CLASSES; + + static { + List classes = new ArrayList(); + classes.add("java.lang."); + classes.add("java.util."); + classes.add("org.springframework.security."); + DEFAULT_ALLOWED_CLASSES = Collections.unmodifiableList(classes); + } + + /** + * A list of classes which are allowed to deserialize. + */ + private final List allowedClasses; + + /** + * Initializes {@link WhitelistedSerializationStrategy} with the list of classes + * which are allowed to deserialize by default. + */ + public WhitelistedSerializationStrategy() { + this(DEFAULT_ALLOWED_CLASSES); + } + + /** + * Initializes {@link WhitelistedSerializationStrategy} with specified allowed classes. + * + * @param allowedClasses The allowed classes for deserialization. + */ + public WhitelistedSerializationStrategy(List allowedClasses) { + this.allowedClasses = Collections.unmodifiableList(allowedClasses); + } + + protected ObjectInputStream createObjectInputStream(byte[] byteArray) throws IOException { + return new WhitelistedObjectInputStream(new ByteArrayInputStream(byteArray), + Thread.currentThread().getContextClassLoader(), allowedClasses); + } + + /** + * Special ObjectInputStream subclass that checks if classes are allowed to deserialize. The class + * should be configured with a whitelist of only allowed (safe) classes to deserialize. + */ + private static class WhitelistedObjectInputStream extends ObjectInputStream { + + /** + * The list of classes which are allowed for deserialization. + */ + private final List allowedClasses; + + /** + * The class loader to use for loading local classes. + */ + private final ClassLoader classLoader; + + /** + * Create a new WhitelistedObjectInputStream for the given InputStream, class loader and + * allowed class names. + * + * @param in The InputStream to read from. + * @param classLoader The ClassLoader to use for loading local classes. + * @param allowedClasses The list of allowed classes for deserialization. + * @throws IOException If something went wrong. + */ + private WhitelistedObjectInputStream(InputStream in, ClassLoader classLoader, List allowedClasses) + throws IOException { + super(in); + this.classLoader = classLoader; + this.allowedClasses = Collections.unmodifiableList(allowedClasses); + } + + /** + * Resolve the class only if it's allowed to deserialize. + * + * @see ObjectInputStream#resolveClass(ObjectStreamClass) + */ + @Override + protected Class resolveClass(ObjectStreamClass classDesc) + throws IOException, ClassNotFoundException { + if (isProhibited(classDesc.getName())) { + throw new NotSerializableException("Not allowed to deserialize " + classDesc.getName()); + } + if (this.classLoader != null) { + return ClassUtils.forName(classDesc.getName(), this.classLoader); + } + return super.resolveClass(classDesc); + } + + /** + * Check if the class is allowed to be deserialized. + * + * @param className The class to check. + * @return True if the class is not allowed to be deserialized, false otherwise. + */ + private boolean isProhibited(String className) { + for (String allowedClass : this.allowedClasses) { + if (className.startsWith(allowedClass)) { + return false; + } + } + return true; + } + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/builders/ClientDetailsServiceBuilder.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/builders/ClientDetailsServiceBuilder.java new file mode 100644 index 000000000..5296f1a55 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/builders/ClientDetailsServiceBuilder.java @@ -0,0 +1,226 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.config.annotation.builders; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.security.config.annotation.SecurityBuilder; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; + +/** + * Builder for OAuth2 client details service. Can be used to construct either an in-memory or a JDBC implementation of + * the {@link ClientDetailsService} and populate it with data. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class ClientDetailsServiceBuilder> extends + SecurityConfigurerAdapter implements SecurityBuilder { + + private List clientBuilders = new ArrayList(); + + public InMemoryClientDetailsServiceBuilder inMemory() throws Exception { + return new InMemoryClientDetailsServiceBuilder(); + } + + public JdbcClientDetailsServiceBuilder jdbc() throws Exception { + return new JdbcClientDetailsServiceBuilder(); + } + + @SuppressWarnings("rawtypes") + public ClientDetailsServiceBuilder clients(final ClientDetailsService clientDetailsService) throws Exception { + return new ClientDetailsServiceBuilder() { + @Override + public ClientDetailsService build() throws Exception { + return clientDetailsService; + } + }; + } + + public ClientBuilder withClient(String clientId) { + ClientBuilder clientBuilder = new ClientBuilder(clientId); + this.clientBuilders.add(clientBuilder); + return clientBuilder; + } + + @Override + public ClientDetailsService build() throws Exception { + for (ClientBuilder clientDetailsBldr : clientBuilders) { + addClient(clientDetailsBldr.clientId, clientDetailsBldr.build()); + } + return performBuild(); + } + + protected void addClient(String clientId, ClientDetails build) { + } + + protected ClientDetailsService performBuild() { + throw new UnsupportedOperationException("Cannot build client services (maybe use inMemory() or jdbc())."); + } + + public final class ClientBuilder { + private final String clientId; + + private Collection authorizedGrantTypes = new LinkedHashSet(); + + private Collection authorities = new LinkedHashSet(); + + private Integer accessTokenValiditySeconds; + + private Integer refreshTokenValiditySeconds; + + private Collection scopes = new LinkedHashSet(); + + private Collection autoApproveScopes = new HashSet(); + + private String secret; + + private Set registeredRedirectUris = new HashSet(); + + private Set resourceIds = new HashSet(); + + private boolean autoApprove; + + private Map additionalInformation = new LinkedHashMap(); + + private ClientDetails build() { + BaseClientDetails result = new BaseClientDetails(); + result.setClientId(clientId); + result.setAuthorizedGrantTypes(authorizedGrantTypes); + result.setAccessTokenValiditySeconds(accessTokenValiditySeconds); + result.setRefreshTokenValiditySeconds(refreshTokenValiditySeconds); + result.setRegisteredRedirectUri(registeredRedirectUris); + result.setClientSecret(secret); + result.setScope(scopes); + result.setAuthorities(AuthorityUtils.createAuthorityList(authorities.toArray(new String[authorities.size()]))); + result.setResourceIds(resourceIds); + result.setAdditionalInformation(additionalInformation); + if (autoApprove) { + result.setAutoApproveScopes(scopes); + } + else { + result.setAutoApproveScopes(autoApproveScopes); + } + return result; + } + + public ClientBuilder resourceIds(String... resourceIds) { + for (String resourceId : resourceIds) { + this.resourceIds.add(resourceId); + } + return this; + } + + public ClientBuilder redirectUris(String... registeredRedirectUris) { + for (String redirectUri : registeredRedirectUris) { + this.registeredRedirectUris.add(redirectUri); + } + return this; + } + + public ClientBuilder authorizedGrantTypes(String... authorizedGrantTypes) { + for (String grant : authorizedGrantTypes) { + this.authorizedGrantTypes.add(grant); + } + return this; + } + + public ClientBuilder accessTokenValiditySeconds(int accessTokenValiditySeconds) { + this.accessTokenValiditySeconds = accessTokenValiditySeconds; + return this; + } + + public ClientBuilder refreshTokenValiditySeconds(int refreshTokenValiditySeconds) { + this.refreshTokenValiditySeconds = refreshTokenValiditySeconds; + return this; + } + + public ClientBuilder secret(String secret) { + this.secret = secret; + return this; + } + + public ClientBuilder scopes(String... scopes) { + for (String scope : scopes) { + this.scopes.add(scope); + } + return this; + } + + public ClientBuilder authorities(String... authorities) { + for (String authority : authorities) { + this.authorities.add(authority); + } + return this; + } + + public ClientBuilder autoApprove(boolean autoApprove) { + this.autoApprove = autoApprove; + return this; + } + + public ClientBuilder autoApprove(String... scopes) { + for (String scope : scopes) { + this.autoApproveScopes.add(scope); + } + return this; + } + + public ClientBuilder additionalInformation(Map map) { + this.additionalInformation.putAll(map); + return this; + } + + public ClientBuilder additionalInformation(String... pairs) { + for (String pair : pairs) { + String separator = ":"; + if (!pair.contains(separator) && pair.contains("=")) { + separator = "="; + } + int index = pair.indexOf(separator); + String key = pair.substring(0, index > 0 ? index : pair.length()); + String value = index > 0 ? pair.substring(index+1) : null; + this.additionalInformation.put(key, (Object) value); + } + return this; + } + + public ClientDetailsServiceBuilder and() { + return ClientDetailsServiceBuilder.this; + } + + private ClientBuilder(String clientId) { + this.clientId = clientId; + } + + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/builders/InMemoryClientDetailsServiceBuilder.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/builders/InMemoryClientDetailsServiceBuilder.java new file mode 100644 index 000000000..f5ff55989 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/builders/InMemoryClientDetailsServiceBuilder.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.config.annotation.builders; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class InMemoryClientDetailsServiceBuilder extends + ClientDetailsServiceBuilder { + + private Map clientDetails = new HashMap(); + + @Override + protected void addClient(String clientId, ClientDetails value) { + clientDetails.put(clientId, value); + } + + @Override + protected ClientDetailsService performBuild() { + InMemoryClientDetailsService clientDetailsService = new InMemoryClientDetailsService(); + clientDetailsService.setClientDetailsStore(clientDetails); + return clientDetailsService; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/builders/JdbcClientDetailsServiceBuilder.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/builders/JdbcClientDetailsServiceBuilder.java new file mode 100644 index 000000000..af9d2cc8a --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/builders/JdbcClientDetailsServiceBuilder.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.config.annotation.builders; + +import java.util.HashSet; +import java.util.Set; + +import javax.sql.DataSource; + +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; +import org.springframework.util.Assert; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class JdbcClientDetailsServiceBuilder extends ClientDetailsServiceBuilder { + + private Set clientDetails = new HashSet(); + + private DataSource dataSource; + + private PasswordEncoder passwordEncoder; // for writing client secrets + + public JdbcClientDetailsServiceBuilder dataSource(DataSource dataSource) { + this.dataSource = dataSource; + return this; + } + + public JdbcClientDetailsServiceBuilder passwordEncoder(PasswordEncoder passwordEncoder) { + this.passwordEncoder = passwordEncoder; + return this; + } + + @Override + protected void addClient(String clientId, ClientDetails value) { + clientDetails.add(value); + } + + @Override + protected ClientDetailsService performBuild() { + Assert.state(dataSource != null, "You need to provide a DataSource"); + JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource); + if (passwordEncoder != null) { + // This is used to encode secrets as they are added to the database (if it isn't set then the user has top + // pass in pre-encoded secrets) + clientDetailsService.setPasswordEncoder(passwordEncoder); + } + for (ClientDetails client : clientDetails) { + clientDetailsService.addClientDetails(client); + } + return clientDetailsService; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/configuration/ClientDetailsServiceConfiguration.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/configuration/ClientDetailsServiceConfiguration.java new file mode 100644 index 000000000..da4f0a251 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/configuration/ClientDetailsServiceConfiguration.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.config.annotation.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.security.oauth2.config.annotation.builders.ClientDetailsServiceBuilder; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.provider.ClientDetailsService; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Rob Winch + * + */ +@Configuration +@Deprecated +public class ClientDetailsServiceConfiguration { + + @SuppressWarnings("rawtypes") + private ClientDetailsServiceConfigurer configurer = new ClientDetailsServiceConfigurer(new ClientDetailsServiceBuilder()); + + @Bean + public ClientDetailsServiceConfigurer clientDetailsServiceConfigurer() { + return configurer; + } + + @Bean + @Lazy + @Scope(proxyMode=ScopedProxyMode.INTERFACES) + public ClientDetailsService clientDetailsService() throws Exception { + return configurer.and().build(); + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/configurers/ClientDetailsServiceConfigurer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/configurers/ClientDetailsServiceConfigurer.java new file mode 100644 index 000000000..84a080b08 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/configurers/ClientDetailsServiceConfigurer.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.config.annotation.configurers; + +import javax.sql.DataSource; + +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.builders.ClientDetailsServiceBuilder; +import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder; +import org.springframework.security.oauth2.config.annotation.builders.JdbcClientDetailsServiceBuilder; +import org.springframework.security.oauth2.provider.ClientDetailsService; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Rob Winch + * + */ +@Deprecated +public class ClientDetailsServiceConfigurer extends + SecurityConfigurerAdapter> { + + public ClientDetailsServiceConfigurer(ClientDetailsServiceBuilder builder) { + setBuilder(builder); + } + + public ClientDetailsServiceBuilder withClientDetails(ClientDetailsService clientDetailsService) throws Exception { + setBuilder(getBuilder().clients(clientDetailsService)); + return this.and(); + } + + public InMemoryClientDetailsServiceBuilder inMemory() throws Exception { + InMemoryClientDetailsServiceBuilder next = getBuilder().inMemory(); + setBuilder(next); + return next; + } + public JdbcClientDetailsServiceBuilder jdbc(DataSource dataSource) throws Exception { + JdbcClientDetailsServiceBuilder next = getBuilder().jdbc().dataSource(dataSource); + setBuilder(next); + return next; + } + + @Override + public void init(ClientDetailsServiceBuilder builder) throws Exception { + } + + @Override + public void configure(ClientDetailsServiceBuilder builder) throws Exception { + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerConfigurer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerConfigurer.java new file mode 100644 index 000000000..3e01c32ce --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerConfigurer.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013-2014 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.config.annotation.web.configuration; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.ClientDetailsService; + +/** + * Convenient strategy for configuring an OAUth2 Authorization Server. Beans of this type are applied to the Spring + * context automatically if you {@link EnableAuthorizationServer @EnableAuthorizationServer}. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public interface AuthorizationServerConfigurer { + + /** + * Configure the security of the Authorization Server, which means in practical terms the /oauth/token endpoint. The + * /oauth/authorize endpoint also needs to be secure, but that is a normal user-facing endpoint and should be + * secured the same way as the rest of your UI, so is not covered here. The default settings cover the most common + * requirements, following recommendations from the OAuth2 spec, so you don't need to do anything here to get a + * basic server up and running. + * + * @param security a fluent configurer for security features + */ + void configure(AuthorizationServerSecurityConfigurer security) throws Exception; + + /** + * Configure the {@link ClientDetailsService}, e.g. declaring individual clients and their properties. Note that + * password grant is not enabled (even if some clients are allowed it) unless an {@link AuthenticationManager} is + * supplied to the {@link #configure(AuthorizationServerEndpointsConfigurer)}. At least one client, or a fully + * formed custom {@link ClientDetailsService} must be declared or the server will not start. + * + * @param clients the client details configurer + */ + void configure(ClientDetailsServiceConfigurer clients) throws Exception; + + /** + * Configure the non-security features of the Authorization Server endpoints, like token store, token + * customizations, user approvals and grant types. You shouldn't need to do anything by default, unless you need + * password grants, in which case you need to provide an {@link AuthenticationManager}. + * + * @param endpoints the endpoints configurer + */ + void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception; + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerConfigurerAdapter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerConfigurerAdapter.java new file mode 100644 index 000000000..9e5e3d537 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerConfigurerAdapter.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013-2014 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.config.annotation.web.configuration; + +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer { + + @Override + public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + } + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerEndpointsConfiguration.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerEndpointsConfiguration.java new file mode 100644 index 000000000..ccc18971d --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerEndpointsConfiguration.java @@ -0,0 +1,273 @@ +/* + * Copyright 2013-2014 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.config.annotation.web.configuration; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.AbstractFactoryBean; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpMethod; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration.TokenKeyEndpointRegistrar; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.OAuth2RequestValidator; +import org.springframework.security.oauth2.provider.TokenGranter; +import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; +import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; +import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint; +import org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint; +import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping; +import org.springframework.security.oauth2.provider.endpoint.RedirectResolver; +import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint; +import org.springframework.security.oauth2.provider.endpoint.TokenKeyEndpoint; +import org.springframework.security.oauth2.provider.endpoint.WhitelabelApprovalEndpoint; +import org.springframework.security.oauth2.provider.endpoint.WhitelabelErrorEndpoint; +import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; +import org.springframework.security.oauth2.provider.token.ConsumerTokenServices; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.stereotype.Component; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Configuration +@Import(TokenKeyEndpointRegistrar.class) +@Deprecated +public class AuthorizationServerEndpointsConfiguration { + + private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer(); + + @Autowired + private ClientDetailsService clientDetailsService; + + @Autowired + private List configurers = Collections.emptyList(); + + @PostConstruct + public void init() { + for (AuthorizationServerConfigurer configurer : configurers) { + try { + configurer.configure(endpoints); + } catch (Exception e) { + throw new IllegalStateException("Cannot configure endpoints", e); + } + } + endpoints.setClientDetailsService(clientDetailsService); + } + + @Bean + public AuthorizationEndpoint authorizationEndpoint() throws Exception { + AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint(); + FrameworkEndpointHandlerMapping mapping = getEndpointsConfigurer().getFrameworkEndpointHandlerMapping(); + authorizationEndpoint.setUserApprovalPage(extractPath(mapping, "/oauth/confirm_access")); + authorizationEndpoint.setProviderExceptionHandler(exceptionTranslator()); + authorizationEndpoint.setErrorPage(extractPath(mapping, "/oauth/error")); + authorizationEndpoint.setTokenGranter(tokenGranter()); + authorizationEndpoint.setClientDetailsService(clientDetailsService); + authorizationEndpoint.setAuthorizationCodeServices(authorizationCodeServices()); + authorizationEndpoint.setOAuth2RequestFactory(oauth2RequestFactory()); + authorizationEndpoint.setOAuth2RequestValidator(oauth2RequestValidator()); + authorizationEndpoint.setUserApprovalHandler(userApprovalHandler()); + authorizationEndpoint.setRedirectResolver(redirectResolver()); + return authorizationEndpoint; + } + + @Bean + public TokenEndpoint tokenEndpoint() throws Exception { + TokenEndpoint tokenEndpoint = new TokenEndpoint(); + tokenEndpoint.setClientDetailsService(clientDetailsService); + tokenEndpoint.setProviderExceptionHandler(exceptionTranslator()); + tokenEndpoint.setTokenGranter(tokenGranter()); + tokenEndpoint.setOAuth2RequestFactory(oauth2RequestFactory()); + tokenEndpoint.setOAuth2RequestValidator(oauth2RequestValidator()); + tokenEndpoint.setAllowedRequestMethods(allowedTokenEndpointRequestMethods()); + return tokenEndpoint; + } + + @Bean + public CheckTokenEndpoint checkTokenEndpoint() { + CheckTokenEndpoint endpoint = new CheckTokenEndpoint(getEndpointsConfigurer().getResourceServerTokenServices()); + endpoint.setAccessTokenConverter(getEndpointsConfigurer().getAccessTokenConverter()); + endpoint.setExceptionTranslator(exceptionTranslator()); + return endpoint; + } + + @Bean + public WhitelabelApprovalEndpoint whitelabelApprovalEndpoint() { + return new WhitelabelApprovalEndpoint(); + } + + @Bean + public WhitelabelErrorEndpoint whitelabelErrorEndpoint() { + return new WhitelabelErrorEndpoint(); + } + + @Bean + public FrameworkEndpointHandlerMapping oauth2EndpointHandlerMapping() throws Exception { + return getEndpointsConfigurer().getFrameworkEndpointHandlerMapping(); + } + + @Bean + public FactoryBean consumerTokenServices() throws Exception { + return new AbstractFactoryBean() { + + @Override + public Class getObjectType() { + return ConsumerTokenServices.class; + } + + @Override + protected ConsumerTokenServices createInstance() throws Exception { + return getEndpointsConfigurer().getConsumerTokenServices(); + } + }; + } + + /** + * This needs to be a @Bean so that it can be + * @Transactional (in case the token store supports them). If + * you are overriding the token services in an + * {@link AuthorizationServerConfigurer} consider making it a + * @Bean for the same reason (assuming you need transactions, + * e.g. for a JDBC token store). + * + * @return an AuthorizationServerTokenServices + */ + @Bean + public FactoryBean defaultAuthorizationServerTokenServices() { + return new AuthorizationServerTokenServicesFactoryBean(endpoints); + } + + public AuthorizationServerEndpointsConfigurer getEndpointsConfigurer() { + if (!endpoints.isTokenServicesOverride()) { + try { + endpoints.tokenServices(endpoints.getDefaultAuthorizationServerTokenServices()); + } + catch (Exception e) { + throw new BeanCreationException("Cannot create token services", e); + } + } + return endpoints; + } + + private Set allowedTokenEndpointRequestMethods() { + return getEndpointsConfigurer().getAllowedTokenEndpointRequestMethods(); + } + + private OAuth2RequestFactory oauth2RequestFactory() throws Exception { + return getEndpointsConfigurer().getOAuth2RequestFactory(); + } + + private UserApprovalHandler userApprovalHandler() throws Exception { + return getEndpointsConfigurer().getUserApprovalHandler(); + } + + private OAuth2RequestValidator oauth2RequestValidator() throws Exception { + return getEndpointsConfigurer().getOAuth2RequestValidator(); + } + + private AuthorizationCodeServices authorizationCodeServices() throws Exception { + return getEndpointsConfigurer().getAuthorizationCodeServices(); + } + + private WebResponseExceptionTranslator exceptionTranslator() { + return getEndpointsConfigurer().getExceptionTranslator(); + } + + private RedirectResolver redirectResolver() { + return getEndpointsConfigurer().getRedirectResolver(); + } + + private TokenGranter tokenGranter() throws Exception { + return getEndpointsConfigurer().getTokenGranter(); + } + + private String extractPath(FrameworkEndpointHandlerMapping mapping, String page) { + String path = mapping.getPath(page); + if (path.contains(":")) { + return path; + } + return "forward:" + path; + } + + protected static class AuthorizationServerTokenServicesFactoryBean + extends AbstractFactoryBean { + + private AuthorizationServerEndpointsConfigurer endpoints; + + protected AuthorizationServerTokenServicesFactoryBean() { + } + + public AuthorizationServerTokenServicesFactoryBean( + AuthorizationServerEndpointsConfigurer endpoints) { + this.endpoints = endpoints; + } + + @Override + public Class getObjectType() { + return AuthorizationServerTokenServices.class; + } + + @Override + protected AuthorizationServerTokenServices createInstance() throws Exception { + return endpoints.getDefaultAuthorizationServerTokenServices(); + } + } + + @Component + protected static class TokenKeyEndpointRegistrar implements BeanDefinitionRegistryPostProcessor { + + private BeanDefinitionRegistry registry; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, + JwtAccessTokenConverter.class, false, false); + if (names.length > 0) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(TokenKeyEndpoint.class); + builder.addConstructorArgReference(names[0]); + registry.registerBeanDefinition(TokenKeyEndpoint.class.getName(), builder.getBeanDefinition()); + } + } + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + this.registry = registry; + } + + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerSecurityConfiguration.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerSecurityConfiguration.java new file mode 100644 index 000000000..a95b4bcf3 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerSecurityConfiguration.java @@ -0,0 +1,111 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.config.annotation.web.configuration; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.config.annotation.configuration.ClientDetailsServiceConfiguration; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping; + +import java.util.Collections; +import java.util.List; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Rob Winch + * @author Dave Syer + * + */ +@Configuration +@Order(0) +@Import({ ClientDetailsServiceConfiguration.class, AuthorizationServerEndpointsConfiguration.class }) +@Deprecated +public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Autowired + private List configurers = Collections.emptyList(); + + @Autowired + private ClientDetailsService clientDetailsService; + + @Autowired + private AuthorizationServerEndpointsConfiguration endpoints; + + @Autowired + public void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception { + for (AuthorizationServerConfigurer configurer : configurers) { + configurer.configure(clientDetails); + } + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // Over-riding to make sure this.disableLocalConfigureAuthenticationBldr = false + // This will ensure that when this configurer builds the AuthenticationManager it will not attempt + // to find another 'Global' AuthenticationManager in the ApplicationContext (if available), + // and set that as the parent of this 'Local' AuthenticationManager. + // This AuthenticationManager should only be wired up with an AuthenticationProvider + // composed of the ClientDetailsService (wired in this configuration) for authenticating 'clients' only. + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer(); + FrameworkEndpointHandlerMapping handlerMapping = endpoints.oauth2EndpointHandlerMapping(); + http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping); + configure(configurer); + http.apply(configurer); + String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token"); + String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key"); + String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token"); + if (!endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) { + UserDetailsService userDetailsService = http.getSharedObject(UserDetailsService.class); + endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService); + } + // @formatter:off + http + .authorizeRequests() + .antMatchers(tokenEndpointPath).fullyAuthenticated() + .antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess()) + .antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess()) + .and() + .requestMatchers() + .antMatchers(tokenEndpointPath, tokenKeyPath, checkTokenPath) + .and() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER); + // @formatter:on + http.setSharedObject(ClientDetailsService.class, clientDetailsService); + } + + protected void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { + for (AuthorizationServerConfigurer configurer : configurers) { + configurer.configure(oauthServer); + } + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/EnableAuthorizationServer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/EnableAuthorizationServer.java new file mode 100644 index 000000000..68ce85496 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/EnableAuthorizationServer.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2014 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.config.annotation.web.configuration; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint; +import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * Convenience annotation for enabling an Authorization Server (i.e. an {@link AuthorizationEndpoint} and a + * {@link TokenEndpoint}) in the current application context, which must be a {@link DispatcherServlet} context. Many + * features of the server can be customized using @Beans of type {@link AuthorizationServerConfigurer} + * (e.g. by extending {@link AuthorizationServerConfigurerAdapter}). The user is responsible for securing the + * Authorization Endpoint (/oauth/authorize) using normal Spring Security features ({@link EnableWebSecurity + * @EnableWebSecurity} etc.), but the Token Endpoint (/oauth/token) will be automatically secured using HTTP Basic + * authentication on the client's credentials. Clients must be registered by providing a + * {@link ClientDetailsService} through one or more AuthorizationServerConfigurers. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class}) +@Deprecated +public @interface EnableAuthorizationServer { + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/EnableOAuth2Client.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/EnableOAuth2Client.java new file mode 100644 index 000000000..2b6a2c933 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/EnableOAuth2Client.java @@ -0,0 +1,66 @@ +/* + * Copyright 2013-2014 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.config.annotation.web.configuration; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.token.AccessTokenRequest; +import org.springframework.web.filter.DelegatingFilterProxy; + +/** + * Enable configuration for an OAuth2 client in a web application that uses Spring Security and wants to use the + * Authorization Code Grant from one or more OAuth2 Authorization servers. To take advantage of this feature you need a + * global servlet filter in your application of the {@link DelegatingFilterProxy} that delegates to a bean named + * "oauth2ClientContextFilter". Once that filter is in place your client app can use another bean provided by this + * annotation (an {@link AccessTokenRequest}) to create an {@link OAuth2RestTemplate}, e.g. + * + *

+ * @Configuration
+ * @EnableOAuth2Client
+ * public class RemoteResourceConfiguration {
+ * 
+ * 	@Bean
+ *  public OAuth2RestOperations restTemplate(OAuth2ClientContext oauth2ClientContext) {
+ * 		return new OAuth2RestTemplate(remote(), oauth2ClientContext);
+ * 	}
+ * 
+ * }
+ * 
+ * + * Client apps that use client credentials grants do not need the AccessTokenRequest or the scoped RestOperations (the + * state is global for the app), but they should still use the filter to trigger the OAuth2RestOperations to obtain a + * token when necessary. Apps that use password grants need to set the authentication properties in the + * OAuth2ProtectedResourceDetails before using the RestOperations, and this means the resource details themselves also + * have to be per session (assuming there are multiple users in the system). + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(OAuth2ClientConfiguration.class) +@Deprecated +public @interface EnableOAuth2Client { + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/EnableResourceServer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/EnableResourceServer.java new file mode 100644 index 000000000..7ceef0307 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/EnableResourceServer.java @@ -0,0 +1,53 @@ +/* + * Copyright 2013-2014 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.config.annotation.web.configuration; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +/** + * Convenient annotation for OAuth2 Resource Servers, enabling a Spring Security filter that authenticates requests via + * an incoming OAuth2 token. Users should add this annotation and provide a @Bean of type + * {@link ResourceServerConfigurer} (e.g. via {@link ResourceServerConfigurerAdapter}) that specifies the details of the + * resource (URL paths and resource id). In order to use this filter you must {@link EnableWebSecurity + * @EnableWebSecurity} somewhere in your application, either in the same place as you use this annotation, or + * somewhere else. + * + *

+ * The annotation creates a {@link WebSecurityConfigurerAdapter} with a hard-coded {@link Order} (of 3). It's not + * possible to change the order right now owing to technical limitations in Spring, so you must avoid using order=3 in + * other WebSecurityConfigurerAdapters in your application (Spring Security will let you know if you forget). + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(ResourceServerConfiguration.class) +@Deprecated +public @interface EnableResourceServer { + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/OAuth2ClientConfiguration.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/OAuth2ClientConfiguration.java new file mode 100644 index 000000000..a618d95ba --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/OAuth2ClientConfiguration.java @@ -0,0 +1,74 @@ +/* + * Copyright 2013-2014 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.config.annotation.web.configuration; + +import java.util.Map; + +import javax.annotation.Resource; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; +import org.springframework.security.oauth2.client.OAuth2ClientContext; +import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter; +import org.springframework.security.oauth2.client.token.AccessTokenRequest; +import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Configuration +@Deprecated +public class OAuth2ClientConfiguration { + + @Bean + public OAuth2ClientContextFilter oauth2ClientContextFilter() { + OAuth2ClientContextFilter filter = new OAuth2ClientContextFilter(); + return filter; + } + + @Bean + @Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES) + protected AccessTokenRequest accessTokenRequest(@Value("#{request.parameterMap}") + Map parameters, @Value("#{request.getAttribute('currentUri')}") + String currentUri) { + DefaultAccessTokenRequest request = new DefaultAccessTokenRequest(parameters); + request.setCurrentUri(currentUri); + return request; + } + + @Configuration + protected static class OAuth2ClientContextConfiguration { + + @Resource + @Qualifier("accessTokenRequest") + private AccessTokenRequest accessTokenRequest; + + @Bean + @Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES) + public OAuth2ClientContext oauth2ClientContext() { + return new DefaultOAuth2ClientContext(accessTokenRequest); + } + + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/ResourceServerConfiguration.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/ResourceServerConfiguration.java new file mode 100644 index 000000000..9e1224846 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/ResourceServerConfiguration.java @@ -0,0 +1,210 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.config.annotation.web.configuration; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.aop.framework.Advised; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.security.authentication.AnonymousAuthenticationProvider; +import org.springframework.security.authentication.AuthenticationEventPublisher; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping; +import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.ReflectionUtils; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Configuration +@Deprecated +public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter implements Ordered { + + private int order = 3; + + @Autowired(required = false) + private TokenStore tokenStore; + + @Autowired(required = false) + private AuthenticationEventPublisher eventPublisher; + + @Autowired(required = false) + private Map tokenServices; + + @Autowired + private ApplicationContext context; + + private List configurers = Collections.emptyList(); + + @Autowired(required = false) + private AuthorizationServerEndpointsConfiguration endpoints; + + @Override + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.order = order; + } + + /** + * @param configurers the configurers to set + */ + @Autowired(required = false) + public void setConfigurers(List configurers) { + this.configurers = configurers; + } + + private static class NotOAuthRequestMatcher implements RequestMatcher { + + private FrameworkEndpointHandlerMapping mapping; + + public NotOAuthRequestMatcher(FrameworkEndpointHandlerMapping mapping) { + this.mapping = mapping; + } + + @Override + public boolean matches(HttpServletRequest request) { + String requestPath = getRequestPath(request); + for (String path : mapping.getPaths()) { + if (requestPath.startsWith(mapping.getPath(path))) { + return false; + } + } + return true; + } + + private String getRequestPath(HttpServletRequest request) { + String url = request.getServletPath(); + + if (request.getPathInfo() != null) { + url += request.getPathInfo(); + } + + return url; + } + + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer(); + ResourceServerTokenServices services = resolveTokenServices(); + if (services != null) { + resources.tokenServices(services); + } + else { + if (tokenStore != null) { + resources.tokenStore(tokenStore); + } + else if (endpoints != null) { + resources.tokenStore(endpoints.getEndpointsConfigurer().getTokenStore()); + } + } + if (eventPublisher != null) { + resources.eventPublisher(eventPublisher); + } + for (ResourceServerConfigurer configurer : configurers) { + configurer.configure(resources); + } + // @formatter:off + http.authenticationProvider(new AnonymousAuthenticationProvider("default")) + // N.B. exceptionHandling is duplicated in resources.configure() so that + // it works + .exceptionHandling() + .accessDeniedHandler(resources.getAccessDeniedHandler()).and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + .csrf().disable(); + // @formatter:on + http.apply(resources); + if (endpoints != null) { + // Assume we are in an Authorization Server + http.requestMatcher(new NotOAuthRequestMatcher(endpoints.oauth2EndpointHandlerMapping())); + } + for (ResourceServerConfigurer configurer : configurers) { + // Delegates can add authorizeRequests() here + configurer.configure(http); + } + if (configurers.isEmpty()) { + // Add anyRequest() last as a fall back. Spring Security would + // replace an existing anyRequest() matcher with this one, so to + // avoid that we only add it if the user hasn't configured anything. + http.authorizeRequests().anyRequest().authenticated(); + } + } + + private ResourceServerTokenServices resolveTokenServices() { + if (tokenServices == null || tokenServices.size() == 0) { + return null; + } + if (tokenServices.size() == 1) { + return tokenServices.values().iterator().next(); + } + if (tokenServices.size() == 2) { + // Maybe they are the ones provided natively + Iterator iter = tokenServices.values().iterator(); + ResourceServerTokenServices one = iter.next(); + ResourceServerTokenServices two = iter.next(); + if (elementsEqual(one, two)) { + return one; + } + } + return context.getBean(ResourceServerTokenServices.class); + } + + private boolean elementsEqual(Object one, Object two) { + // They might just be equal + if (one == two) { + return true; + } + Object targetOne = findTarget(one); + Object targetTwo = findTarget(two); + return targetOne == targetTwo; + } + + private Object findTarget(Object item) { + Object current = item; + while (current instanceof Advised) { + try { + current = ((Advised) current).getTargetSource().getTarget(); + } + catch (Exception e) { + ReflectionUtils.rethrowRuntimeException(e); + } + } + return current; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/ResourceServerConfigurer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/ResourceServerConfigurer.java new file mode 100644 index 000000000..490e01562 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/ResourceServerConfigurer.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013-2014 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.config.annotation.web.configuration; + +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.expression.OAuth2WebSecurityExpressionHandler; + +/** + * Configurer interface for @EnableResourceServer classes. Implement this interface to adjust the access + * rules and paths that are protected by OAuth2 security. Applications may provide multiple instances of this interface, + * and in general (like with other Security configurers), if more than one configures the same property, then the last + * one wins. The configurers are sorted by {@link Order} before being applied. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public interface ResourceServerConfigurer { + + /** + * Add resource-server specific properties (like a resource id). The defaults should work for many applications, but + * you might want to change at least the resource id. + * + * @param resources configurer for the resource server + * @throws Exception if there is a problem + */ + void configure(ResourceServerSecurityConfigurer resources) throws Exception; + + /** + * Use this to configure the access rules for secure resources. By default all resources not in "/oauth/**" + * are protected (but no specific rules about scopes are given, for instance). You also get an + * {@link OAuth2WebSecurityExpressionHandler} by default. + * + * @param http the current http filter configuration + * @throws Exception if there is a problem + */ + void configure(HttpSecurity http) throws Exception; + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/ResourceServerConfigurerAdapter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/ResourceServerConfigurerAdapter.java new file mode 100644 index 000000000..cea39d37b --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/ResourceServerConfigurerAdapter.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013-2014 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.config.annotation.web.configuration; + +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class ResourceServerConfigurerAdapter implements ResourceServerConfigurer { + + @Override + public void configure(ResourceServerSecurityConfigurer resources) throws Exception { + } + + @Override + public void configure(HttpSecurity http) throws Exception { + http.authorizeRequests().anyRequest().authenticated(); + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java new file mode 100644 index 000000000..6785fb430 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java @@ -0,0 +1,604 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * 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.config.annotation.web.configurers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.security.oauth2.common.util.ProxyCreator; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.CompositeTokenGranter; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.OAuth2RequestValidator; +import org.springframework.security.oauth2.provider.TokenGranter; +import org.springframework.security.oauth2.provider.TokenRequest; +import org.springframework.security.oauth2.provider.approval.ApprovalStore; +import org.springframework.security.oauth2.provider.approval.ApprovalStoreUserApprovalHandler; +import org.springframework.security.oauth2.provider.approval.TokenApprovalStore; +import org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler; +import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; +import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter; +import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService; +import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; +import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter; +import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices; +import org.springframework.security.oauth2.provider.endpoint.DefaultRedirectResolver; +import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping; +import org.springframework.security.oauth2.provider.endpoint.RedirectResolver; +import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator; +import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; +import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter; +import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter; +import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator; +import org.springframework.security.oauth2.provider.token.AccessTokenConverter; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; +import org.springframework.security.oauth2.provider.token.ConsumerTokenServices; +import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; +import org.springframework.security.oauth2.provider.token.TokenEnhancer; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.web.context.request.WebRequestInterceptor; +import org.springframework.web.servlet.HandlerInterceptor; + +/** + * Configure the properties and enhanced functionality of the Authorization Server endpoints. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Rob Winch + * @author Dave Syer + * @since 2.0 + */ +@Deprecated +public final class AuthorizationServerEndpointsConfigurer { + + private AuthorizationServerTokenServices tokenServices; + + private ConsumerTokenServices consumerTokenServices; + + private AuthorizationCodeServices authorizationCodeServices; + + private ResourceServerTokenServices resourceTokenServices; + + private TokenStore tokenStore; + + private TokenEnhancer tokenEnhancer; + + private AccessTokenConverter accessTokenConverter; + + private ApprovalStore approvalStore; + + private TokenGranter tokenGranter; + + private OAuth2RequestFactory requestFactory; + + private OAuth2RequestValidator requestValidator; + + private UserApprovalHandler userApprovalHandler; + + private AuthenticationManager authenticationManager; + + private ClientDetailsService clientDetailsService; + + private String prefix; + + private Map patternMap = new HashMap(); + + private Set allowedTokenEndpointRequestMethods = new HashSet(); + + private FrameworkEndpointHandlerMapping frameworkEndpointHandlerMapping; + + private boolean approvalStoreDisabled; + + private List interceptors = new ArrayList(); + + private DefaultTokenServices defaultTokenServices; + + private UserDetailsService userDetailsService; + + private boolean tokenServicesOverride = false; + + private boolean userDetailsServiceOverride = false; + + private boolean reuseRefreshToken = true; + + private WebResponseExceptionTranslator exceptionTranslator; + + private RedirectResolver redirectResolver; + + public AuthorizationServerTokenServices getTokenServices() { + return ProxyCreator.getProxy(AuthorizationServerTokenServices.class, + new ObjectFactory() { + @Override + public AuthorizationServerTokenServices getObject() throws BeansException { + return tokenServices(); + } + }); + } + + public TokenStore getTokenStore() { + return tokenStore(); + } + + public TokenEnhancer getTokenEnhancer() { + return tokenEnhancer; + } + + public AccessTokenConverter getAccessTokenConverter() { + return accessTokenConverter(); + } + + public ApprovalStore getApprovalStore() { + return approvalStore; + } + + public ClientDetailsService getClientDetailsService() { + return ProxyCreator.getProxy(ClientDetailsService.class, new ObjectFactory() { + @Override + public ClientDetailsService getObject() throws BeansException { + return clientDetailsService(); + } + }); + } + + public OAuth2RequestFactory getOAuth2RequestFactory() { + return ProxyCreator.getProxy(OAuth2RequestFactory.class, new ObjectFactory() { + @Override + public OAuth2RequestFactory getObject() throws BeansException { + return requestFactory(); + } + }); + } + + public OAuth2RequestValidator getOAuth2RequestValidator() { + return requestValidator(); + } + + public UserApprovalHandler getUserApprovalHandler() { + return userApprovalHandler(); + } + + public AuthorizationServerEndpointsConfigurer tokenStore(TokenStore tokenStore) { + this.tokenStore = tokenStore; + return this; + } + + public AuthorizationServerEndpointsConfigurer tokenEnhancer(TokenEnhancer tokenEnhancer) { + this.tokenEnhancer = tokenEnhancer; + return this; + } + + public AuthorizationServerEndpointsConfigurer reuseRefreshTokens(boolean reuseRefreshToken) { + this.reuseRefreshToken = reuseRefreshToken; + return this; + } + + public AuthorizationServerEndpointsConfigurer accessTokenConverter(AccessTokenConverter accessTokenConverter) { + this.accessTokenConverter = accessTokenConverter; + return this; + } + + public AuthorizationServerEndpointsConfigurer tokenServices(AuthorizationServerTokenServices tokenServices) { + this.tokenServices = tokenServices; + if (tokenServices != null) { + this.tokenServicesOverride = true; + } + return this; + } + + public AuthorizationServerEndpointsConfigurer redirectResolver(RedirectResolver redirectResolver) { + this.redirectResolver = redirectResolver; + return this; + } + + public boolean isTokenServicesOverride() { + return tokenServicesOverride; + } + + public boolean isUserDetailsServiceOverride() { + return userDetailsServiceOverride; + } + + public AuthorizationServerEndpointsConfigurer userApprovalHandler(UserApprovalHandler approvalHandler) { + this.userApprovalHandler = approvalHandler; + return this; + } + + public AuthorizationServerEndpointsConfigurer approvalStore(ApprovalStore approvalStore) { + if (approvalStoreDisabled) { + throw new IllegalStateException("ApprovalStore was disabled"); + } + this.approvalStore = approvalStore; + return this; + } + + /** + * Explicitly disable the approval store, even if one would normally be added automatically (usually when JWT is not + * used). Without an approval store the user can only be asked to approve or deny a grant without any more granular + * decisions. + * + * @return this for fluent builder + */ + public AuthorizationServerEndpointsConfigurer approvalStoreDisabled() { + this.approvalStoreDisabled = true; + return this; + } + + public AuthorizationServerEndpointsConfigurer prefix(String prefix) { + this.prefix = prefix; + return this; + } + + public AuthorizationServerEndpointsConfigurer pathMapping(String defaultPath, String customPath) { + this.patternMap.put(defaultPath, customPath); + return this; + } + + public AuthorizationServerEndpointsConfigurer addInterceptor(HandlerInterceptor interceptor) { + this.interceptors.add(interceptor); + return this; + } + + public AuthorizationServerEndpointsConfigurer addInterceptor(WebRequestInterceptor interceptor) { + this.interceptors.add(interceptor); + return this; + } + + public AuthorizationServerEndpointsConfigurer exceptionTranslator(WebResponseExceptionTranslator exceptionTranslator) { + this.exceptionTranslator = exceptionTranslator; + return this; + } + + /** + * The AuthenticationManager for the password grant. + * + * @param authenticationManager an AuthenticationManager, fully initialized + * @return this for a fluent style + */ + public AuthorizationServerEndpointsConfigurer authenticationManager(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + return this; + } + + public AuthorizationServerEndpointsConfigurer tokenGranter(TokenGranter tokenGranter) { + this.tokenGranter = tokenGranter; + return this; + } + + /** + * N.B. this method is not part of the public API. To set up a custom ClientDetailsService please use + * {@link AuthorizationServerConfigurerAdapter#configure(ClientDetailsServiceConfigurer)}. + */ + public void setClientDetailsService(ClientDetailsService clientDetailsService) { + this.clientDetailsService = clientDetailsService; + } + + public AuthorizationServerEndpointsConfigurer requestFactory(OAuth2RequestFactory requestFactory) { + this.requestFactory = requestFactory; + return this; + } + + public AuthorizationServerEndpointsConfigurer requestValidator(OAuth2RequestValidator requestValidator) { + this.requestValidator = requestValidator; + return this; + } + + public AuthorizationServerEndpointsConfigurer authorizationCodeServices( + AuthorizationCodeServices authorizationCodeServices) { + this.authorizationCodeServices = authorizationCodeServices; + return this; + } + + public AuthorizationServerEndpointsConfigurer allowedTokenEndpointRequestMethods(HttpMethod... requestMethods) { + Collections.addAll(allowedTokenEndpointRequestMethods, requestMethods); + return this; + } + + public AuthorizationServerEndpointsConfigurer userDetailsService(UserDetailsService userDetailsService) { + if (userDetailsService != null) { + this.userDetailsService = userDetailsService; + this.userDetailsServiceOverride = true; + } + return this; + } + + public ConsumerTokenServices getConsumerTokenServices() { + return consumerTokenServices(); + } + + public ResourceServerTokenServices getResourceServerTokenServices() { + return resourceTokenServices(); + } + + public AuthorizationCodeServices getAuthorizationCodeServices() { + return authorizationCodeServices(); + } + + public Set getAllowedTokenEndpointRequestMethods() { + return allowedTokenEndpointRequestMethods(); + } + + public OAuth2RequestValidator getRequestValidator() { + return requestValidator(); + } + + public TokenGranter getTokenGranter() { + return tokenGranter(); + } + + public FrameworkEndpointHandlerMapping getFrameworkEndpointHandlerMapping() { + return frameworkEndpointHandlerMapping(); + } + + public WebResponseExceptionTranslator getExceptionTranslator() { + return exceptionTranslator(); + } + + public RedirectResolver getRedirectResolver() { + return redirectResolver(); + } + + private ResourceServerTokenServices resourceTokenServices() { + if (resourceTokenServices == null) { + if (tokenServices instanceof ResourceServerTokenServices) { + return (ResourceServerTokenServices) tokenServices; + } + resourceTokenServices = createDefaultTokenServices(); + } + return resourceTokenServices; + } + + private Set allowedTokenEndpointRequestMethods() { + // HTTP POST should be the only allowed endpoint request method by default. + if (allowedTokenEndpointRequestMethods.isEmpty()) { + allowedTokenEndpointRequestMethods.add(HttpMethod.POST); + } + return allowedTokenEndpointRequestMethods; + } + + private ConsumerTokenServices consumerTokenServices() { + if (consumerTokenServices == null) { + if (tokenServices instanceof ConsumerTokenServices) { + return (ConsumerTokenServices) tokenServices; + } + consumerTokenServices = createDefaultTokenServices(); + } + return consumerTokenServices; + } + + private AuthorizationServerTokenServices tokenServices() { + if (tokenServices != null) { + return tokenServices; + } + this.tokenServices = createDefaultTokenServices(); + return tokenServices; + } + + public AuthorizationServerTokenServices getDefaultAuthorizationServerTokenServices() { + if (defaultTokenServices != null) { + return defaultTokenServices; + } + this.defaultTokenServices = createDefaultTokenServices(); + return this.defaultTokenServices; + } + + private DefaultTokenServices createDefaultTokenServices() { + DefaultTokenServices tokenServices = new DefaultTokenServices(); + tokenServices.setTokenStore(tokenStore()); + tokenServices.setSupportRefreshToken(true); + tokenServices.setReuseRefreshToken(reuseRefreshToken); + tokenServices.setClientDetailsService(clientDetailsService()); + tokenServices.setTokenEnhancer(tokenEnhancer()); + addUserDetailsService(tokenServices, this.userDetailsService); + return tokenServices; + } + + private TokenEnhancer tokenEnhancer() { + if (this.tokenEnhancer == null && accessTokenConverter() instanceof JwtAccessTokenConverter) { + tokenEnhancer = (TokenEnhancer) accessTokenConverter; + } + return this.tokenEnhancer; + } + + private AccessTokenConverter accessTokenConverter() { + if (this.accessTokenConverter == null) { + accessTokenConverter = new DefaultAccessTokenConverter(); + } + return this.accessTokenConverter; + } + + private TokenStore tokenStore() { + if (tokenStore == null) { + if (accessTokenConverter() instanceof JwtAccessTokenConverter) { + this.tokenStore = new JwtTokenStore((JwtAccessTokenConverter) accessTokenConverter()); + } + else { + this.tokenStore = new InMemoryTokenStore(); + } + } + return this.tokenStore; + } + + private ApprovalStore approvalStore() { + if (approvalStore == null && tokenStore() != null && !isApprovalStoreDisabled()) { + TokenApprovalStore tokenApprovalStore = new TokenApprovalStore(); + tokenApprovalStore.setTokenStore(tokenStore()); + this.approvalStore = tokenApprovalStore; + } + return this.approvalStore; + } + + private boolean isApprovalStoreDisabled() { + return approvalStoreDisabled || (tokenStore() instanceof JwtTokenStore); + } + + private ClientDetailsService clientDetailsService() { + if (clientDetailsService == null) { + this.clientDetailsService = new InMemoryClientDetailsService(); + } + if (this.defaultTokenServices != null) { + addUserDetailsService(defaultTokenServices, userDetailsService); + } + return this.clientDetailsService; + } + + private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) { + if (userDetailsService != null) { + PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider(); + provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper( + userDetailsService)); + tokenServices + .setAuthenticationManager(new ProviderManager(Arrays. asList(provider))); + } + } + + private UserApprovalHandler userApprovalHandler() { + if (userApprovalHandler == null) { + if (approvalStore() != null) { + ApprovalStoreUserApprovalHandler handler = new ApprovalStoreUserApprovalHandler(); + handler.setApprovalStore(approvalStore()); + handler.setRequestFactory(requestFactory()); + handler.setClientDetailsService(clientDetailsService); + this.userApprovalHandler = handler; + } + else if (tokenStore() != null) { + TokenStoreUserApprovalHandler userApprovalHandler = new TokenStoreUserApprovalHandler(); + userApprovalHandler.setTokenStore(tokenStore()); + userApprovalHandler.setClientDetailsService(clientDetailsService()); + userApprovalHandler.setRequestFactory(requestFactory()); + this.userApprovalHandler = userApprovalHandler; + } + else { + throw new IllegalStateException("Either a TokenStore or an ApprovalStore must be provided"); + } + } + return this.userApprovalHandler; + } + + private AuthorizationCodeServices authorizationCodeServices() { + if (authorizationCodeServices == null) { + authorizationCodeServices = new InMemoryAuthorizationCodeServices(); + } + return authorizationCodeServices; + } + + private WebResponseExceptionTranslator exceptionTranslator() { + if (exceptionTranslator != null) { + return exceptionTranslator; + } + exceptionTranslator = new DefaultWebResponseExceptionTranslator(); + return exceptionTranslator; + } + + private RedirectResolver redirectResolver() { + if (redirectResolver != null) { + return redirectResolver; + } + redirectResolver = new DefaultRedirectResolver(); + return redirectResolver; + } + + private OAuth2RequestFactory requestFactory() { + if (requestFactory != null) { + return requestFactory; + } + requestFactory = new DefaultOAuth2RequestFactory(clientDetailsService()); + return requestFactory; + } + + private OAuth2RequestValidator requestValidator() { + if (requestValidator != null) { + return requestValidator; + } + requestValidator = new DefaultOAuth2RequestValidator(); + return requestValidator; + } + + private List getDefaultTokenGranters() { + ClientDetailsService clientDetails = clientDetailsService(); + AuthorizationServerTokenServices tokenServices = tokenServices(); + AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices(); + OAuth2RequestFactory requestFactory = requestFactory(); + + List tokenGranters = new ArrayList(); + tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails, + requestFactory)); + tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory)); + ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory); + tokenGranters.add(implicit); + tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory)); + if (authenticationManager != null) { + tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, + clientDetails, requestFactory)); + } + return tokenGranters; + } + + private TokenGranter tokenGranter() { + if (tokenGranter == null) { + tokenGranter = new TokenGranter() { + private CompositeTokenGranter delegate; + + @Override + public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { + if (delegate == null) { + delegate = new CompositeTokenGranter(getDefaultTokenGranters()); + } + return delegate.grant(grantType, tokenRequest); + } + }; + } + return tokenGranter; + } + + private FrameworkEndpointHandlerMapping frameworkEndpointHandlerMapping() { + if (frameworkEndpointHandlerMapping == null) { + frameworkEndpointHandlerMapping = new FrameworkEndpointHandlerMapping(); + frameworkEndpointHandlerMapping.setMappings(patternMap); + frameworkEndpointHandlerMapping.setPrefix(prefix); + frameworkEndpointHandlerMapping.setInterceptors(interceptors.toArray()); + } + return frameworkEndpointHandlerMapping; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerSecurityConfigurer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerSecurityConfigurer.java new file mode 100644 index 000000000..c0962ac73 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerSecurityConfigurer.java @@ -0,0 +1,287 @@ +/* + * 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.config.annotation.web.configurers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.servlet.Filter; + +import org.springframework.http.MediaType; +import org.springframework.security.authentication.AuthenticationEventPublisher; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter; +import org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService; +import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping; +import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler; +import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.context.NullSecurityContextRepository; +import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.accept.ContentNegotiationStrategy; +import org.springframework.web.accept.HeaderContentNegotiationStrategy; + +/** + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Rob Winch + * @author Dave Syer + * @since 2.0 + */ +@Deprecated +public final class AuthorizationServerSecurityConfigurer extends + SecurityConfigurerAdapter { + + private AuthenticationEntryPoint authenticationEntryPoint; + + private AccessDeniedHandler accessDeniedHandler = new OAuth2AccessDeniedHandler(); + + private PasswordEncoder passwordEncoder; // for client secrets + + private String realm = "oauth2/client"; + + private boolean allowFormAuthenticationForClients = false; + + private String tokenKeyAccess = "denyAll()"; + + private String checkTokenAccess = "denyAll()"; + + private boolean sslOnly = false; + + /** + * Custom authentication filters for the TokenEndpoint. Filters will be set upstream of the default + * BasicAuthenticationFilter. + */ + private List tokenEndpointAuthenticationFilters = new ArrayList(); + + private List authenticationProviders = new ArrayList(); + + private AuthenticationEventPublisher authenticationEventPublisher; + + public AuthorizationServerSecurityConfigurer sslOnly() { + this.sslOnly = true; + return this; + } + + public AuthorizationServerSecurityConfigurer allowFormAuthenticationForClients() { + this.allowFormAuthenticationForClients = true; + return this; + } + + public AuthorizationServerSecurityConfigurer realm(String realm) { + this.realm = realm; + return this; + } + + public AuthorizationServerSecurityConfigurer passwordEncoder(PasswordEncoder passwordEncoder) { + this.passwordEncoder = passwordEncoder; + return this; + } + + public AuthorizationServerSecurityConfigurer authenticationEntryPoint( + AuthenticationEntryPoint authenticationEntryPoint) { + this.authenticationEntryPoint = authenticationEntryPoint; + return this; + } + + public AuthorizationServerSecurityConfigurer accessDeniedHandler(AccessDeniedHandler accessDeniedHandler) { + this.accessDeniedHandler = accessDeniedHandler; + return this; + } + + /** + * Authentication provider(s) to use with the {@link AuthenticationManager}. + * Adding an authentication provider here will replace the default {@link DaoAuthenticationProvider}. + * + * @param authenticationProvider the authentication provider to add + */ + public AuthorizationServerSecurityConfigurer addAuthenticationProvider(AuthenticationProvider authenticationProvider) { + Assert.notNull(authenticationProvider, "authenticationProvider must not be null"); + this.authenticationProviders.add(authenticationProvider); + return this; + } + + /** + * {@link AuthenticationEventPublisher} to use with the {@link AuthenticationManager}. + * + * @param authenticationEventPublisher the {@link AuthenticationEventPublisher} to use + */ + public AuthorizationServerSecurityConfigurer authenticationEventPublisher(AuthenticationEventPublisher authenticationEventPublisher) { + Assert.notNull(authenticationEventPublisher, "authenticationEventPublisher must not be null"); + this.authenticationEventPublisher = authenticationEventPublisher; + return this; + } + + public AuthorizationServerSecurityConfigurer tokenKeyAccess(String tokenKeyAccess) { + this.tokenKeyAccess = tokenKeyAccess; + return this; + } + + public AuthorizationServerSecurityConfigurer checkTokenAccess(String checkTokenAccess) { + this.checkTokenAccess = checkTokenAccess; + return this; + } + + public String getTokenKeyAccess() { + return tokenKeyAccess; + } + + public String getCheckTokenAccess() { + return checkTokenAccess; + } + + @Override + public void init(HttpSecurity http) throws Exception { + registerDefaultAuthenticationEntryPoint(http); + AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class); + if (authenticationEventPublisher != null) { + builder.authenticationEventPublisher(authenticationEventPublisher); + } + if (authenticationProviders.isEmpty()) { + if (passwordEncoder != null) { + builder.userDetailsService(new ClientDetailsUserDetailsService(clientDetailsService())) + .passwordEncoder(passwordEncoder()); + } else { + builder.userDetailsService(new ClientDetailsUserDetailsService(clientDetailsService())); + } + } else { + for (AuthenticationProvider provider: authenticationProviders) { + builder.authenticationProvider(provider); + } + } + http.securityContext().securityContextRepository(new NullSecurityContextRepository()).and().csrf().disable() + .httpBasic().authenticationEntryPoint(this.authenticationEntryPoint).realmName(realm); + if (sslOnly) { + http.requiresChannel().anyRequest().requiresSecure(); + } + } + + private PasswordEncoder passwordEncoder() { + return new PasswordEncoder() { + + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + return StringUtils.hasText(encodedPassword) ? passwordEncoder.matches(rawPassword, encodedPassword) + : true; + } + + @Override + public String encode(CharSequence rawPassword) { + return passwordEncoder.encode(rawPassword); + } + }; + } + + @SuppressWarnings("unchecked") + private void registerDefaultAuthenticationEntryPoint(HttpSecurity http) { + ExceptionHandlingConfigurer exceptionHandling = http + .getConfigurer(ExceptionHandlingConfigurer.class); + if (exceptionHandling == null) { + return; + } + if (authenticationEntryPoint==null) { + BasicAuthenticationEntryPoint basicEntryPoint = new BasicAuthenticationEntryPoint(); + basicEntryPoint.setRealmName(realm); + authenticationEntryPoint = basicEntryPoint; + } + ContentNegotiationStrategy contentNegotiationStrategy = http.getSharedObject(ContentNegotiationStrategy.class); + if (contentNegotiationStrategy == null) { + contentNegotiationStrategy = new HeaderContentNegotiationStrategy(); + } + MediaTypeRequestMatcher preferredMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy, + MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, + MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.MULTIPART_FORM_DATA, + MediaType.TEXT_XML); + preferredMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL)); + exceptionHandling.defaultAuthenticationEntryPointFor(postProcess(authenticationEntryPoint), preferredMatcher); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + + // ensure this is initialized + frameworkEndpointHandlerMapping(); + if (allowFormAuthenticationForClients) { + clientCredentialsTokenEndpointFilter(http); + } + + for (Filter filter : tokenEndpointAuthenticationFilters) { + http.addFilterBefore(filter, BasicAuthenticationFilter.class); + } + + http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); + } + + private ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter(HttpSecurity http) { + ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter = new ClientCredentialsTokenEndpointFilter( + frameworkEndpointHandlerMapping().getServletPath("/oauth/token")); + clientCredentialsTokenEndpointFilter + .setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); + OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint(); + authenticationEntryPoint.setTypeName("Form"); + authenticationEntryPoint.setRealmName(realm); + clientCredentialsTokenEndpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint); + clientCredentialsTokenEndpointFilter = postProcess(clientCredentialsTokenEndpointFilter); + http.addFilterBefore(clientCredentialsTokenEndpointFilter, BasicAuthenticationFilter.class); + return clientCredentialsTokenEndpointFilter; + } + + private ClientDetailsService clientDetailsService() { + return getBuilder().getSharedObject(ClientDetailsService.class); + } + + private FrameworkEndpointHandlerMapping frameworkEndpointHandlerMapping() { + return getBuilder().getSharedObject(FrameworkEndpointHandlerMapping.class); + } + + /** + * Adds a new custom authentication filter for the TokenEndpoint. Filters will be set upstream of the default + * BasicAuthenticationFilter. + * + * @param filter + */ + public void addTokenEndpointAuthenticationFilter(Filter filter) { + this.tokenEndpointAuthenticationFilters.add(filter); + } + + /** + * Sets a new list of custom authentication filters for the TokenEndpoint. Filters will be set upstream of the + * default BasicAuthenticationFilter. + * + * @param filters The authentication filters to set. + */ + public void tokenEndpointAuthenticationFilters(List filters) { + Assert.notNull(filters, "Custom authentication filter list must not be null"); + this.tokenEndpointAuthenticationFilters = new ArrayList(filters); + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/ResourceServerSecurityConfigurer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/ResourceServerSecurityConfigurer.java new file mode 100644 index 000000000..e6b6d36b6 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/ResourceServerSecurityConfigurer.java @@ -0,0 +1,276 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * 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.config.annotation.web.configurers; + +import java.util.Collections; + +import org.springframework.http.MediaType; +import org.springframework.security.access.expression.SecurityExpressionHandler; +import org.springframework.security.authentication.AuthenticationDetailsSource; +import org.springframework.security.authentication.AuthenticationEventPublisher; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetailsSource; +import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager; +import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter; +import org.springframework.security.oauth2.provider.authentication.TokenExtractor; +import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler; +import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; +import org.springframework.security.oauth2.provider.expression.OAuth2WebSecurityExpressionHandler; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.FilterInvocation; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; +import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; +import org.springframework.util.Assert; +import org.springframework.web.accept.ContentNegotiationStrategy; +import org.springframework.web.accept.HeaderContentNegotiationStrategy; + +import javax.servlet.http.HttpServletRequest; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Rob Winch + * @author Dave Syer + * + * @since 2.0.0 + */ +@Deprecated +public final class ResourceServerSecurityConfigurer extends + SecurityConfigurerAdapter { + + private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint(); + + private AccessDeniedHandler accessDeniedHandler = new OAuth2AccessDeniedHandler(); + + private OAuth2AuthenticationProcessingFilter resourcesServerFilter; + + private AuthenticationManager authenticationManager; + + private AuthenticationEventPublisher eventPublisher = null; + + private ResourceServerTokenServices resourceTokenServices; + + private TokenStore tokenStore = new InMemoryTokenStore(); + + private String resourceId = "oauth2-resource"; + + private SecurityExpressionHandler expressionHandler = new OAuth2WebSecurityExpressionHandler(); + + private TokenExtractor tokenExtractor; + + private AuthenticationDetailsSource authenticationDetailsSource; + + private boolean stateless = true; + + public ResourceServerSecurityConfigurer() { + resourceId(resourceId); + } + + private ClientDetailsService clientDetails() { + return getBuilder().getSharedObject(ClientDetailsService.class); + } + + public TokenStore getTokenStore() { + return tokenStore; + } + + /** + * Flag to indicate that only token-based authentication is allowed on these resources. + * @param stateless the flag value (default true) + * @return this (for fluent builder) + */ + public ResourceServerSecurityConfigurer stateless(boolean stateless) { + this.stateless = stateless; + return this; + } + + public ResourceServerSecurityConfigurer authenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { + this.authenticationEntryPoint = authenticationEntryPoint; + return this; + } + + public ResourceServerSecurityConfigurer accessDeniedHandler(AccessDeniedHandler accessDeniedHandler) { + this.accessDeniedHandler = accessDeniedHandler; + return this; + } + + public ResourceServerSecurityConfigurer tokenStore(TokenStore tokenStore) { + Assert.state(tokenStore != null, "TokenStore cannot be null"); + this.tokenStore = tokenStore; + return this; + } + + public ResourceServerSecurityConfigurer eventPublisher(AuthenticationEventPublisher eventPublisher) { + Assert.state(eventPublisher != null, "AuthenticationEventPublisher cannot be null"); + this.eventPublisher = eventPublisher; + return this; + } + + public ResourceServerSecurityConfigurer expressionHandler( + SecurityExpressionHandler expressionHandler) { + Assert.state(expressionHandler != null, "SecurityExpressionHandler cannot be null"); + this.expressionHandler = expressionHandler; + return this; + } + + public ResourceServerSecurityConfigurer tokenExtractor(TokenExtractor tokenExtractor) { + Assert.state(tokenExtractor != null, "TokenExtractor cannot be null"); + this.tokenExtractor = tokenExtractor; + return this; + } + + /** + * Sets a custom {@link AuthenticationDetailsSource} to use as a source + * of authentication details. The default is {@link OAuth2AuthenticationDetailsSource}. + * + * @param authenticationDetailsSource the custom {@link AuthenticationDetailsSource} to use + * @return {@link ResourceServerSecurityConfigurer} for additional customization + */ + public ResourceServerSecurityConfigurer authenticationDetailsSource( + AuthenticationDetailsSource authenticationDetailsSource) { + Assert.state(authenticationDetailsSource != null, "AuthenticationDetailsSource cannot be null"); + this.authenticationDetailsSource = authenticationDetailsSource; + return this; + } + + public ResourceServerSecurityConfigurer authenticationManager(AuthenticationManager authenticationManager) { + Assert.state(authenticationManager != null, "AuthenticationManager cannot be null"); + this.authenticationManager = authenticationManager; + return this; + } + + public ResourceServerSecurityConfigurer tokenServices(ResourceServerTokenServices tokenServices) { + Assert.state(tokenServices != null, "ResourceServerTokenServices cannot be null"); + this.resourceTokenServices = tokenServices; + return this; + } + + @Override + public void init(HttpSecurity http) throws Exception { + registerDefaultAuthenticationEntryPoint(http); + } + + @SuppressWarnings("unchecked") + private void registerDefaultAuthenticationEntryPoint(HttpSecurity http) { + ExceptionHandlingConfigurer exceptionHandling = http + .getConfigurer(ExceptionHandlingConfigurer.class); + if (exceptionHandling == null) { + return; + } + ContentNegotiationStrategy contentNegotiationStrategy = http.getSharedObject(ContentNegotiationStrategy.class); + if (contentNegotiationStrategy == null) { + contentNegotiationStrategy = new HeaderContentNegotiationStrategy(); + } + MediaTypeRequestMatcher preferredMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy, + MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, + MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.MULTIPART_FORM_DATA, + MediaType.TEXT_XML); + preferredMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL)); + exceptionHandling.defaultAuthenticationEntryPointFor(postProcess(authenticationEntryPoint), preferredMatcher); + } + + public ResourceServerSecurityConfigurer resourceId(String resourceId) { + this.resourceId = resourceId; + if (authenticationEntryPoint instanceof OAuth2AuthenticationEntryPoint) { + ((OAuth2AuthenticationEntryPoint) authenticationEntryPoint).setRealmName(resourceId); + } + return this; + } + + @Override + public void configure(HttpSecurity http) throws Exception { + + AuthenticationManager oauthAuthenticationManager = oauthAuthenticationManager(http); + resourcesServerFilter = new OAuth2AuthenticationProcessingFilter(); + resourcesServerFilter.setAuthenticationEntryPoint(authenticationEntryPoint); + resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager); + if (eventPublisher != null) { + resourcesServerFilter.setAuthenticationEventPublisher(eventPublisher); + } + if (tokenExtractor != null) { + resourcesServerFilter.setTokenExtractor(tokenExtractor); + } + if (authenticationDetailsSource != null) { + resourcesServerFilter.setAuthenticationDetailsSource(authenticationDetailsSource); + } + resourcesServerFilter = postProcess(resourcesServerFilter); + resourcesServerFilter.setStateless(stateless); + + // @formatter:off + http + .authorizeRequests().expressionHandler(expressionHandler) + .and() + .addFilterBefore(resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class) + .exceptionHandling() + .accessDeniedHandler(accessDeniedHandler) + .authenticationEntryPoint(authenticationEntryPoint); + // @formatter:on + } + + private AuthenticationManager oauthAuthenticationManager(HttpSecurity http) { + OAuth2AuthenticationManager oauthAuthenticationManager = new OAuth2AuthenticationManager(); + if (authenticationManager != null) { + if (authenticationManager instanceof OAuth2AuthenticationManager) { + oauthAuthenticationManager = (OAuth2AuthenticationManager) authenticationManager; + } + else { + return authenticationManager; + } + } + oauthAuthenticationManager.setResourceId(resourceId); + oauthAuthenticationManager.setTokenServices(resourceTokenServices(http)); + oauthAuthenticationManager.setClientDetailsService(clientDetails()); + return oauthAuthenticationManager; + } + + private ResourceServerTokenServices resourceTokenServices(HttpSecurity http) { + tokenServices(http); + return this.resourceTokenServices; + } + + private ResourceServerTokenServices tokenServices(HttpSecurity http) { + if (resourceTokenServices != null) { + return resourceTokenServices; + } + DefaultTokenServices tokenServices = new DefaultTokenServices(); + tokenServices.setTokenStore(tokenStore()); + tokenServices.setSupportRefreshToken(true); + tokenServices.setClientDetailsService(clientDetails()); + this.resourceTokenServices = tokenServices; + return tokenServices; + } + + private TokenStore tokenStore() { + Assert.state(tokenStore != null, "TokenStore cannot be null"); + return this.tokenStore; + } + + public AccessDeniedHandler getAccessDeniedHandler() { + return this.accessDeniedHandler; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/AuthorizationServerBeanDefinitionParser.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/AuthorizationServerBeanDefinitionParser.java similarity index 54% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/AuthorizationServerBeanDefinitionParser.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/AuthorizationServerBeanDefinitionParser.java index fb6074beb..78b15f304 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/AuthorizationServerBeanDefinitionParser.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/AuthorizationServerBeanDefinitionParser.java @@ -4,18 +4,17 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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.config; - -import java.util.List; +package org.springframework.security.oauth2.config.xml; import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -24,38 +23,47 @@ import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.config.BeanIds; import org.springframework.security.oauth2.provider.CompositeTokenGranter; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequestManager; import org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler; import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter; import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter; import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices; -import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint; -import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping; -import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint; -import org.springframework.security.oauth2.provider.endpoint.WhitelabelApprovalEndpoint; +import org.springframework.security.oauth2.provider.endpoint.*; import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter; import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter; import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; +import java.util.List; + /** * Parser for the OAuth "provider" element. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ -public class AuthorizationServerBeanDefinitionParser extends ProviderBeanDefinitionParser { +@Deprecated +public class AuthorizationServerBeanDefinitionParser + extends ProviderBeanDefinitionParser { @Override - protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, ParserContext parserContext, - String tokenServicesRef, String serializerRef) { + protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, + ParserContext parserContext, String tokenServicesRef, String serializerRef) { String clientDetailsRef = element.getAttribute("client-details-service-ref"); - String authorizationRequestManagerRef = element.getAttribute("authorization-request-manager-ref"); + String oAuth2RequestFactoryRef = element + .getAttribute("authorization-request-manager-ref"); String tokenEndpointUrl = element.getAttribute("token-endpoint-url"); - String authorizationEndpointUrl = element.getAttribute("authorization-endpoint-url"); + String checkTokenUrl = element.getAttribute("check-token-endpoint-url"); + String enableCheckToken = element.getAttribute("check-token-enabled"); + String authorizationEndpointUrl = element + .getAttribute("authorization-endpoint-url"); String tokenGranterRef = element.getAttribute("token-granter-ref"); String redirectStrategyRef = element.getAttribute("redirect-strategy-ref"); String userApprovalHandlerRef = element.getAttribute("user-approval-handler-ref"); @@ -65,16 +73,44 @@ protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, P String approvalParameter = element.getAttribute("approval-parameter-name"); String redirectResolverRef = element.getAttribute("redirect-resolver-ref"); + String oAuth2RequestValidatorRef = element.getAttribute("request-validator-ref"); + // Create a bean definition speculatively for the auth endpoint BeanDefinitionBuilder authorizationEndpointBean = BeanDefinitionBuilder .rootBeanDefinition(AuthorizationEndpoint.class); + if (!StringUtils.hasText(clientDetailsRef)) { + parserContext.getReaderContext() + .error("ClientDetailsService must be provided", element); + return null; + } + + if (!StringUtils.hasText(oAuth2RequestValidatorRef)) { + oAuth2RequestValidatorRef = "defaultOAuth2RequestValidator"; + BeanDefinitionBuilder oAuth2RequestValidator = BeanDefinitionBuilder + .rootBeanDefinition(DefaultOAuth2RequestValidator.class); + parserContext.getRegistry().registerBeanDefinition(oAuth2RequestValidatorRef, + oAuth2RequestValidator.getBeanDefinition()); + } + authorizationEndpointBean.addPropertyReference("oAuth2RequestValidator", + oAuth2RequestValidatorRef); + + if (!StringUtils.hasText(oAuth2RequestFactoryRef)) { + oAuth2RequestFactoryRef = "oAuth2AuthorizationRequestManager"; + BeanDefinitionBuilder oAuth2RequestManager = BeanDefinitionBuilder + .rootBeanDefinition(DefaultOAuth2RequestFactory.class); + oAuth2RequestManager.addConstructorArgReference(clientDetailsRef); + parserContext.getRegistry().registerBeanDefinition(oAuth2RequestFactoryRef, + oAuth2RequestManager.getBeanDefinition()); + } + ManagedList tokenGranters = null; if (!StringUtils.hasText(tokenGranterRef)) { tokenGranterRef = "oauth2TokenGranter"; BeanDefinitionBuilder tokenGranterBean = BeanDefinitionBuilder .rootBeanDefinition(CompositeTokenGranter.class); - parserContext.getRegistry().registerBeanDefinition(tokenGranterRef, tokenGranterBean.getBeanDefinition()); + parserContext.getRegistry().registerBeanDefinition(tokenGranterRef, + tokenGranterBean.getBeanDefinition()); tokenGranters = new ManagedList(); tokenGranterBean.addConstructorArgValue(tokenGranters); } @@ -82,38 +118,50 @@ protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, P boolean registerAuthorizationEndpoint = false; - Element authorizationCodeElement = DomUtils.getChildElementByTagName(element, "authorization-code"); - if (authorizationCodeElement != null - && !"true".equalsIgnoreCase(authorizationCodeElement.getAttribute("disabled"))) { + Element authorizationCodeElement = DomUtils.getChildElementByTagName(element, + "authorization-code"); + + if (authorizationCodeElement != null && !"true" + .equalsIgnoreCase(authorizationCodeElement.getAttribute("disabled"))) { // authorization code grant configuration. - String authorizationCodeServices = authorizationCodeElement.getAttribute("authorization-code-services-ref"); - String clientTokenCacheRef = authorizationCodeElement.getAttribute("client-token-cache-ref"); + String authorizationCodeServices = authorizationCodeElement + .getAttribute("authorization-code-services-ref"); + String clientTokenCacheRef = authorizationCodeElement + .getAttribute("client-token-cache-ref"); BeanDefinitionBuilder authorizationCodeTokenGranterBean = BeanDefinitionBuilder .rootBeanDefinition(AuthorizationCodeTokenGranter.class); if (StringUtils.hasText(tokenServicesRef)) { - authorizationCodeTokenGranterBean.addConstructorArgReference(tokenServicesRef); + authorizationCodeTokenGranterBean + .addConstructorArgReference(tokenServicesRef); } if (!StringUtils.hasText(authorizationCodeServices)) { authorizationCodeServices = "oauth2AuthorizationCodeServices"; BeanDefinitionBuilder authorizationCodeServicesBean = BeanDefinitionBuilder .rootBeanDefinition(InMemoryAuthorizationCodeServices.class); - parserContext.getRegistry().registerBeanDefinition(authorizationCodeServices, + parserContext.getRegistry().registerBeanDefinition( + authorizationCodeServices, authorizationCodeServicesBean.getBeanDefinition()); } - authorizationEndpointBean.addPropertyReference("authorizationCodeServices", authorizationCodeServices); - authorizationCodeTokenGranterBean.addConstructorArgReference(authorizationCodeServices); - authorizationCodeTokenGranterBean.addConstructorArgReference(clientDetailsRef); + authorizationEndpointBean.addPropertyReference("authorizationCodeServices", + authorizationCodeServices); + authorizationCodeTokenGranterBean + .addConstructorArgReference(authorizationCodeServices); + authorizationCodeTokenGranterBean + .addConstructorArgReference(clientDetailsRef); + authorizationCodeTokenGranterBean + .addConstructorArgReference(oAuth2RequestFactoryRef); if (StringUtils.hasText(clientTokenCacheRef)) { - authorizationEndpointBean.addPropertyReference("clientTokenCache", clientTokenCacheRef); + authorizationEndpointBean.addPropertyReference("clientTokenCache", + clientTokenCacheRef); } - if (StringUtils.hasText(authorizationRequestManagerRef)) { - authorizationEndpointBean.addPropertyReference("authorizationRequestManager", - authorizationRequestManagerRef); + if (StringUtils.hasText(oAuth2RequestFactoryRef)) { + authorizationEndpointBean.addPropertyReference("oAuth2RequestFactory", + oAuth2RequestFactoryRef); } if (tokenGranters != null) { @@ -123,62 +171,71 @@ protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, P registerAuthorizationEndpoint = true; } - if (!StringUtils.hasText(authorizationRequestManagerRef)) { - authorizationRequestManagerRef = "oauth2AuthorizationRequestManager"; - BeanDefinitionBuilder authorizationRequestManager = BeanDefinitionBuilder - .rootBeanDefinition(DefaultAuthorizationRequestManager.class); - authorizationRequestManager.addConstructorArgReference(clientDetailsRef); - parserContext.getRegistry().registerBeanDefinition(authorizationRequestManagerRef, - authorizationRequestManager.getBeanDefinition()); - } - if (tokenGranters != null) { - Element refreshTokenElement = DomUtils.getChildElementByTagName(element, "refresh-token"); - if (refreshTokenElement != null && !"true".equalsIgnoreCase(refreshTokenElement.getAttribute("disabled"))) { + Element refreshTokenElement = DomUtils.getChildElementByTagName(element, + "refresh-token"); + + if (refreshTokenElement != null && !"true" + .equalsIgnoreCase(refreshTokenElement.getAttribute("disabled"))) { BeanDefinitionBuilder refreshTokenGranterBean = BeanDefinitionBuilder .rootBeanDefinition(RefreshTokenGranter.class); refreshTokenGranterBean.addConstructorArgReference(tokenServicesRef); refreshTokenGranterBean.addConstructorArgReference(clientDetailsRef); + refreshTokenGranterBean + .addConstructorArgReference(oAuth2RequestFactoryRef); tokenGranters.add(refreshTokenGranterBean.getBeanDefinition()); } - Element implicitElement = DomUtils.getChildElementByTagName(element, "implicit"); - if (implicitElement != null && !"true".equalsIgnoreCase(implicitElement.getAttribute("disabled"))) { + Element implicitElement = DomUtils.getChildElementByTagName(element, + "implicit"); + if (implicitElement != null && !"true" + .equalsIgnoreCase(implicitElement.getAttribute("disabled"))) { BeanDefinitionBuilder implicitGranterBean = BeanDefinitionBuilder .rootBeanDefinition(ImplicitTokenGranter.class); implicitGranterBean.addConstructorArgReference(tokenServicesRef); implicitGranterBean.addConstructorArgReference(clientDetailsRef); + implicitGranterBean.addConstructorArgReference(oAuth2RequestFactoryRef); tokenGranters.add(implicitGranterBean.getBeanDefinition()); registerAuthorizationEndpoint = true; } - Element clientCredentialsElement = DomUtils.getChildElementByTagName(element, "client-credentials"); - if (clientCredentialsElement != null - && !"true".equalsIgnoreCase(clientCredentialsElement.getAttribute("disabled"))) { + Element clientCredentialsElement = DomUtils.getChildElementByTagName(element, + "client-credentials"); + if (clientCredentialsElement != null && !"true".equalsIgnoreCase( + clientCredentialsElement.getAttribute("disabled"))) { BeanDefinitionBuilder clientCredentialsGranterBean = BeanDefinitionBuilder .rootBeanDefinition(ClientCredentialsTokenGranter.class); clientCredentialsGranterBean.addConstructorArgReference(tokenServicesRef); clientCredentialsGranterBean.addConstructorArgReference(clientDetailsRef); + clientCredentialsGranterBean + .addConstructorArgReference(oAuth2RequestFactoryRef); tokenGranters.add(clientCredentialsGranterBean.getBeanDefinition()); } - Element clientPasswordElement = DomUtils.getChildElementByTagName(element, "password"); - if (clientPasswordElement != null - && !"true".equalsIgnoreCase(clientPasswordElement.getAttribute("disabled"))) { + Element clientPasswordElement = DomUtils.getChildElementByTagName(element, + "password"); + if (clientPasswordElement != null && !"true" + .equalsIgnoreCase(clientPasswordElement.getAttribute("disabled"))) { BeanDefinitionBuilder clientPasswordTokenGranter = BeanDefinitionBuilder .rootBeanDefinition(ResourceOwnerPasswordTokenGranter.class); - String authenticationManagerRef = clientPasswordElement.getAttribute("authentication-manager-ref"); + String authenticationManagerRef = clientPasswordElement + .getAttribute("authentication-manager-ref"); if (!StringUtils.hasText(authenticationManagerRef)) { authenticationManagerRef = BeanIds.AUTHENTICATION_MANAGER; } - clientPasswordTokenGranter.addConstructorArgReference(authenticationManagerRef); + clientPasswordTokenGranter + .addConstructorArgReference(authenticationManagerRef); clientPasswordTokenGranter.addConstructorArgReference(tokenServicesRef); clientPasswordTokenGranter.addConstructorArgReference(clientDetailsRef); + clientPasswordTokenGranter + .addConstructorArgReference(oAuth2RequestFactoryRef); tokenGranters.add(clientPasswordTokenGranter.getBeanDefinition()); } - List customGrantElements = DomUtils.getChildElementsByTagName(element, "custom-grant"); - for(Element customGrantElement: customGrantElements) { - if(!"true".equalsIgnoreCase(customGrantElement.getAttribute("disabled"))) { - String customGranterRef = customGrantElement.getAttribute("token-granter-ref"); - parserContext.getRegistry().getBeanDefinition(customGranterRef); - tokenGranters.add(parserContext.getRegistry().getBeanDefinition(customGranterRef)); + List customGrantElements = DomUtils + .getChildElementsByTagName(element, "custom-grant"); + for (Element customGrantElement : customGrantElements) { + if (!"true" + .equalsIgnoreCase(customGrantElement.getAttribute("disabled"))) { + String customGranterRef = customGrantElement + .getAttribute("token-granter-ref"); + tokenGranters.add(new RuntimeBeanReference(customGranterRef)); } } } @@ -191,60 +248,101 @@ protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, P approvalEndpointBean.getBeanDefinition()); if (!StringUtils.hasText(clientDetailsRef)) { - parserContext.getReaderContext().error("A client details service is mandatory", element); + parserContext.getReaderContext() + .error("A client details service is mandatory", element); } if (StringUtils.hasText(redirectStrategyRef)) { - authorizationEndpointBean.addPropertyReference("redirectStrategy", redirectStrategyRef); + authorizationEndpointBean.addPropertyReference("redirectStrategy", + redirectStrategyRef); } if (StringUtils.hasText(userApprovalHandlerRef)) { - authorizationEndpointBean.addPropertyReference("userApprovalHandler", userApprovalHandlerRef); + authorizationEndpointBean.addPropertyReference("userApprovalHandler", + userApprovalHandlerRef); } - authorizationEndpointBean.addPropertyReference("clientDetailsService", clientDetailsRef); + authorizationEndpointBean.addPropertyReference("clientDetailsService", + clientDetailsRef); if (StringUtils.hasText(redirectResolverRef)) { - authorizationEndpointBean.addPropertyReference("redirectResolver", redirectResolverRef); + authorizationEndpointBean.addPropertyReference("redirectResolver", + redirectResolverRef); } if (StringUtils.hasText(approvalPage)) { - authorizationEndpointBean.addPropertyValue("userApprovalPage", approvalPage); + authorizationEndpointBean.addPropertyValue("userApprovalPage", + approvalPage); } if (StringUtils.hasText(errorPage)) { authorizationEndpointBean.addPropertyValue("errorPage", errorPage); } - parserContext.getRegistry().registerBeanDefinition("oauth2AuthorizationEndpoint", + parserContext.getRegistry().registerBeanDefinition( + "oauth2AuthorizationEndpoint", authorizationEndpointBean.getBeanDefinition()); } // configure the token endpoint - BeanDefinitionBuilder tokenEndpointBean = BeanDefinitionBuilder.rootBeanDefinition(TokenEndpoint.class); + BeanDefinitionBuilder tokenEndpointBean = BeanDefinitionBuilder + .rootBeanDefinition(TokenEndpoint.class); tokenEndpointBean.addPropertyReference("clientDetailsService", clientDetailsRef); tokenEndpointBean.addPropertyReference("tokenGranter", tokenGranterRef); - parserContext.getRegistry() - .registerBeanDefinition("oauth2TokenEndpoint", tokenEndpointBean.getBeanDefinition()); - if (StringUtils.hasText(authorizationRequestManagerRef)) { - tokenEndpointBean.addPropertyReference("authorizationRequestManager", authorizationRequestManagerRef); + authorizationEndpointBean.addPropertyReference("oAuth2RequestValidator", + oAuth2RequestValidatorRef); + parserContext.getRegistry().registerBeanDefinition("oauth2TokenEndpoint", + tokenEndpointBean.getBeanDefinition()); + if (StringUtils.hasText(oAuth2RequestFactoryRef)) { + tokenEndpointBean.addPropertyReference("oAuth2RequestFactory", + oAuth2RequestFactoryRef); + } + if (StringUtils.hasText(oAuth2RequestValidatorRef)) { + tokenEndpointBean.addPropertyReference("oAuth2RequestValidator", + oAuth2RequestValidatorRef); } // Register a handler mapping that can detect the auth server endpoints BeanDefinitionBuilder handlerMappingBean = BeanDefinitionBuilder .rootBeanDefinition(FrameworkEndpointHandlerMapping.class); - if (StringUtils.hasText(tokenEndpointUrl) || StringUtils.hasText(authorizationEndpointUrl)) { - ManagedMap mappings = new ManagedMap(); + ManagedMap mappings = new ManagedMap(); + if (StringUtils.hasText(tokenEndpointUrl) + || StringUtils.hasText(authorizationEndpointUrl)) { if (StringUtils.hasText(tokenEndpointUrl)) { - mappings.put("/oauth/token", new TypedStringValue(tokenEndpointUrl, String.class)); + mappings.put("/oauth/token", + new TypedStringValue(tokenEndpointUrl, String.class)); } if (StringUtils.hasText(authorizationEndpointUrl)) { - mappings.put("/oauth/authorize", new TypedStringValue(authorizationEndpointUrl,String.class)); + mappings.put("/oauth/authorize", + new TypedStringValue(authorizationEndpointUrl, String.class)); + } + if (StringUtils.hasText(approvalPage)) { + mappings.put("/oauth/confirm_access", + new TypedStringValue(approvalPage, String.class)); } + } + if (StringUtils.hasText(enableCheckToken) && enableCheckToken.equals("true")) { + // configure the check token endpoint + BeanDefinitionBuilder checkTokenEndpointBean = BeanDefinitionBuilder + .rootBeanDefinition(CheckTokenEndpoint.class); + checkTokenEndpointBean.addConstructorArgReference(tokenServicesRef); + parserContext.getRegistry().registerBeanDefinition("oauth2CheckTokenEndpoint", + checkTokenEndpointBean.getBeanDefinition()); + + if (StringUtils.hasText(checkTokenUrl)) { + mappings.put("/oauth/check_token", + new TypedStringValue(checkTokenUrl, String.class)); + } + } + if (!mappings.isEmpty()) { handlerMappingBean.addPropertyValue("mappings", mappings); } + if (StringUtils.hasText(approvalParameter) && registerAuthorizationEndpoint) { if (!StringUtils.hasText(userApprovalHandlerRef)) { - BeanDefinitionBuilder userApprovalHandler = BeanDefinitionBuilder.rootBeanDefinition(DefaultUserApprovalHandler.class); - userApprovalHandler.addPropertyValue("approvalParameter", new TypedStringValue(approvalParameter, String.class)); - authorizationEndpointBean.addPropertyValue("userApprovalHandler", userApprovalHandler.getBeanDefinition()); + BeanDefinitionBuilder userApprovalHandler = BeanDefinitionBuilder + .rootBeanDefinition(DefaultUserApprovalHandler.class); + userApprovalHandler.addPropertyValue("approvalParameter", + new TypedStringValue(approvalParameter, String.class)); + authorizationEndpointBean.addPropertyValue("userApprovalHandler", + userApprovalHandler.getBeanDefinition()); } handlerMappingBean.addPropertyValue("approvalParameter", approvalParameter); } @@ -252,7 +350,6 @@ protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, P parserContext.getRegistry().registerBeanDefinition("oauth2HandlerMapping", handlerMappingBean.getBeanDefinition()); - // We aren't defining a filter... return null; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ClientBeanDefinitionParser.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ClientBeanDefinitionParser.java similarity index 85% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ClientBeanDefinitionParser.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ClientBeanDefinitionParser.java index b52d47f0d..a8796ec2f 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ClientBeanDefinitionParser.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ClientBeanDefinitionParser.java @@ -4,14 +4,14 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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.config; +package org.springframework.security.oauth2.config.xml; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -24,10 +24,14 @@ /** * Parser for the OAuth "client" element supporting client apps using {@link OAuth2RestTemplate}. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class ClientBeanDefinitionParser extends AbstractBeanDefinitionParser { @Override diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ClientDetailsServiceBeanDefinitionParser.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ClientDetailsServiceBeanDefinitionParser.java similarity index 85% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ClientDetailsServiceBeanDefinitionParser.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ClientDetailsServiceBeanDefinitionParser.java index b0ae0fd3f..e67945547 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ClientDetailsServiceBeanDefinitionParser.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ClientDetailsServiceBeanDefinitionParser.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.security.oauth2.config; +package org.springframework.security.oauth2.config.xml; import java.util.List; @@ -23,16 +23,20 @@ import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.security.oauth2.provider.BaseClientDetails; -import org.springframework.security.oauth2.provider.InMemoryClientDetailsService; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Andrew McCall */ +@Deprecated public class ClientDetailsServiceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override @@ -81,6 +85,7 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit if (StringUtils.hasText(redirectUri)) { client.addConstructorArgValue(redirectUri); } + client.addPropertyValue("autoApproveScopes", clientElement.getAttribute("autoapprove")); clients.put(clientId, client.getBeanDefinition()); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ConfigUtils.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ConfigUtils.java similarity index 81% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ConfigUtils.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ConfigUtils.java index c4e88206b..fc11e3d9e 100755 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ConfigUtils.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ConfigUtils.java @@ -1,5 +1,6 @@ -package org.springframework.security.oauth2.config; +package org.springframework.security.oauth2.config.xml; +import java.lang.reflect.Method; import java.util.Collections; import java.util.List; @@ -13,6 +14,7 @@ import org.springframework.security.config.BeanIds; import org.springframework.security.config.http.MatcherType; import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; @@ -20,9 +22,18 @@ /** * Common place for OAuth namespace configuration utils. * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@Deprecated public class ConfigUtils { + private static final Method createMatcherMethod3x = ReflectionUtils.findMethod( + MatcherType.class, "createMatcher", String.class, String.class); + private static final Method createMatcherMethod4x = ReflectionUtils.findMethod( + MatcherType.class, "createMatcher", ParserContext.class, String.class, String.class); + private ConfigUtils() { } @@ -57,7 +68,13 @@ public static BeanDefinition createSecurityMetadataSource(Element element, Parse String access = filterPattern.getAttribute("resources"); if (StringUtils.hasText(access)) { - BeanDefinition matcher = matcherType.createMatcher(path, method); + BeanDefinition matcher; + if (createMatcherMethod4x != null) { + matcher = (BeanDefinition)ReflectionUtils.invokeMethod(createMatcherMethod4x, matcherType, pc, path, method); + } else { + matcher = (BeanDefinition)ReflectionUtils.invokeMethod(createMatcherMethod3x, matcherType, path, method); + } + if (access.equals("none")) { invocationDefinitionMap.put(matcher, BeanDefinitionBuilder.rootBeanDefinition(Collections.class).setFactoryMethod("emptyList").getBeanDefinition()); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ExpressionHandlerBeanDefinitionParser.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ExpressionHandlerBeanDefinitionParser.java similarity index 77% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ExpressionHandlerBeanDefinitionParser.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ExpressionHandlerBeanDefinitionParser.java index 839779dc6..fc7880107 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ExpressionHandlerBeanDefinitionParser.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ExpressionHandlerBeanDefinitionParser.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,16 +14,20 @@ * limitations under the License. */ -package org.springframework.security.oauth2.config; +package org.springframework.security.oauth2.config.xml; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; import org.w3c.dom.Element; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class ExpressionHandlerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/OAuth2ClientContextFactoryBean.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/OAuth2ClientContextFactoryBean.java similarity index 88% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/OAuth2ClientContextFactoryBean.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/OAuth2ClientContextFactoryBean.java index 606ea500c..dc865ffc0 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/OAuth2ClientContextFactoryBean.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/OAuth2ClientContextFactoryBean.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -12,7 +12,7 @@ */ -package org.springframework.security.oauth2.config; +package org.springframework.security.oauth2.config.xml; import org.springframework.beans.factory.FactoryBean; import org.springframework.security.oauth2.client.OAuth2ClientContext; @@ -23,10 +23,14 @@ * Convenience factory for OAuth2ClientContext that is aware of the need for a different context if the resource is for a * client credentials grant. Client credentials grants will always have the same credentials for all requests, so * there's no point protecting the context with session and request scopes. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class OAuth2ClientContextFactoryBean implements FactoryBean { private OAuth2ProtectedResourceDetails resource; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/OAuth2SecurityNamespaceHandler.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/OAuth2SecurityNamespaceHandler.java similarity index 83% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/OAuth2SecurityNamespaceHandler.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/OAuth2SecurityNamespaceHandler.java index 7a7509536..b92184402 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/OAuth2SecurityNamespaceHandler.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/OAuth2SecurityNamespaceHandler.java @@ -4,21 +4,25 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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.config; +package org.springframework.security.oauth2.config.xml; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class OAuth2SecurityNamespaceHandler extends NamespaceHandlerSupport { public void init() { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ProviderBeanDefinitionParser.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ProviderBeanDefinitionParser.java similarity index 85% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ProviderBeanDefinitionParser.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ProviderBeanDefinitionParser.java index 08ecaee00..93b16525e 100755 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ProviderBeanDefinitionParser.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ProviderBeanDefinitionParser.java @@ -4,30 +4,34 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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.config; +package org.springframework.security.oauth2.config.xml; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; -import org.springframework.security.oauth2.provider.token.InMemoryTokenStore; +import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; import org.springframework.util.StringUtils; import org.w3c.dom.Element; /** * Parser for the OAuth "provider" element. * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public abstract class ProviderBeanDefinitionParser extends AbstractBeanDefinitionParser { @Override diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ResourceBeanDefinitionParser.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ResourceBeanDefinitionParser.java similarity index 80% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ResourceBeanDefinitionParser.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ResourceBeanDefinitionParser.java index 5afcc7a05..23c76ee29 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ResourceBeanDefinitionParser.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ResourceBeanDefinitionParser.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.security.oauth2.config; +package org.springframework.security.oauth2.config.xml; import java.util.ArrayList; import java.util.Arrays; @@ -29,27 +29,36 @@ import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitResourceDetails; +import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; import org.springframework.security.oauth2.common.AuthenticationScheme; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.util.StringUtils; import org.w3c.dom.Element; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@Deprecated public class ResourceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override protected Class getBeanClass(Element element) { - if ("authorization_code".equals(element.getAttribute("type"))) { + String type = element.getAttribute("type"); + if ("authorization_code".equals(type)) { return AuthorizationCodeResourceDetails.class; } - if ("implicit".equals(element.getAttribute("type"))) { + if ("implicit".equals(type)) { return ImplicitResourceDetails.class; } - if ("client_credentials".equals(element.getAttribute("type"))) { + if ("client_credentials".equals(type)) { return ClientCredentialsResourceDetails.class; } + if ("password".equals(type)) { + return ResourceOwnerPasswordResourceDetails.class; + } return BaseOAuth2ProtectedResourceDetails.class; } @@ -92,15 +101,15 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit String userAuthorizationUri = element.getAttribute("user-authorization-uri"); if (StringUtils.hasText(userAuthorizationUri)) { - if (type.equals("client_credentials")) { - parserContext.getReaderContext().error("The client_credentials grant type does not accept an authorization URI", element); - } else { + if (needsUserAuthorizationUri(type)) { builder.addPropertyValue("userAuthorizationUri", userAuthorizationUri); + } else { + parserContext.getReaderContext().error("The " + type + " grant type does not accept an authorization URI", element); } } else { - if (!type.equals("client_credentials")) { - parserContext.getReaderContext().error("An authorization URI must be supplied for a resource of type " + type, element); - } + if (needsUserAuthorizationUri(type)) { + parserContext.getReaderContext().error("An authorization URI must be supplied for a resource of type " + type, element); + } } String preEstablishedRedirectUri = element.getAttribute("pre-established-redirect-uri"); @@ -139,6 +148,17 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit } builder.addPropertyValue("tokenName", bearerTokenName); + if (type.equals("password")) { + String[] attributeNames = {"username", "password"}; + for (String attributeName : attributeNames) { + String attribute = element.getAttribute(attributeName); + if (StringUtils.hasText(attribute)) { + builder.addPropertyValue(attributeName, attribute); + } else { + parserContext.getReaderContext().error("A " + attributeName + " must be supplied on a resource element of type " + type, element); + } + } + } } /** @@ -168,4 +188,8 @@ public boolean isSingleton() { } + private boolean needsUserAuthorizationUri(String type) { + return type.equals("authorization_code") || type.equals("implicit"); + } + } \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ResourceServerBeanDefinitionParser.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ResourceServerBeanDefinitionParser.java similarity index 54% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ResourceServerBeanDefinitionParser.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ResourceServerBeanDefinitionParser.java index 1c934d981..d6d8d7403 100755 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/ResourceServerBeanDefinitionParser.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/ResourceServerBeanDefinitionParser.java @@ -4,14 +4,14 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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.config; +package org.springframework.security.oauth2.config.xml; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -24,10 +24,14 @@ /** * Parser for the OAuth "resource-server" element. Creates a filter that can be added to the standard Spring Security * filter chain. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class ResourceServerBeanDefinitionParser extends ProviderBeanDefinitionParser { @Override @@ -36,22 +40,50 @@ protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, P String resourceId = element.getAttribute("resource-id"); String entryPointRef = element.getAttribute("entry-point-ref"); + String authenticationManagerRef = element.getAttribute("authentication-manager-ref"); + String tokenExtractorRef = element.getAttribute("token-extractor-ref"); + String entryAuthDetailsSource = element.getAttribute("auth-details-source-ref"); + String stateless = element.getAttribute("stateless"); // configure the protected resource filter BeanDefinitionBuilder protectedResourceFilterBean = BeanDefinitionBuilder .rootBeanDefinition(OAuth2AuthenticationProcessingFilter.class); - BeanDefinitionBuilder authenticationManagerBean = BeanDefinitionBuilder - .rootBeanDefinition(OAuth2AuthenticationManager.class); - authenticationManagerBean.addPropertyReference("tokenServices", tokenServicesRef); - if (StringUtils.hasText(resourceId)) { - authenticationManagerBean.addPropertyValue("resourceId", resourceId); + + if (StringUtils.hasText(authenticationManagerRef)) { + protectedResourceFilterBean.addPropertyReference("authenticationManager", authenticationManagerRef); + } + else { + + BeanDefinitionBuilder authenticationManagerBean = BeanDefinitionBuilder + .rootBeanDefinition(OAuth2AuthenticationManager.class); + + authenticationManagerBean.addPropertyReference("tokenServices", tokenServicesRef); + + if (StringUtils.hasText(resourceId)) { + authenticationManagerBean.addPropertyValue("resourceId", resourceId); + } + + protectedResourceFilterBean.addPropertyValue("authenticationManager", + authenticationManagerBean.getBeanDefinition()); + } - protectedResourceFilterBean.addPropertyValue("authenticationManager", - authenticationManagerBean.getBeanDefinition()); + if (StringUtils.hasText(entryPointRef)) { protectedResourceFilterBean.addPropertyReference("authenticationEntryPoint", entryPointRef); } + if (StringUtils.hasText(entryAuthDetailsSource)) { + protectedResourceFilterBean.addPropertyReference("authenticationDetailsSource", entryAuthDetailsSource); + } + + if (StringUtils.hasText(tokenExtractorRef)) { + protectedResourceFilterBean.addPropertyReference("tokenExtractor", tokenExtractorRef); + } + + if (StringUtils.hasText(stateless)) { + protectedResourceFilterBean.addPropertyValue("stateless", stateless); + } + return protectedResourceFilterBean.getBeanDefinition(); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/RestTemplateBeanDefinitionParser.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/RestTemplateBeanDefinitionParser.java similarity index 93% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/RestTemplateBeanDefinitionParser.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/RestTemplateBeanDefinitionParser.java index 0074a5312..7054d99df 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/RestTemplateBeanDefinitionParser.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/RestTemplateBeanDefinitionParser.java @@ -4,13 +4,13 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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.config; +package org.springframework.security.oauth2.config.xml; import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.factory.config.BeanDefinition; @@ -25,9 +25,13 @@ import org.w3c.dom.Element; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class RestTemplateBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/WebExpressionHandlerBeanDefinitionParser.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/WebExpressionHandlerBeanDefinitionParser.java similarity index 77% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/WebExpressionHandlerBeanDefinitionParser.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/WebExpressionHandlerBeanDefinitionParser.java index 095faab3f..def339b0d 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/WebExpressionHandlerBeanDefinitionParser.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/WebExpressionHandlerBeanDefinitionParser.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,16 +14,20 @@ * limitations under the License. */ -package org.springframework.security.oauth2.config; +package org.springframework.security.oauth2.config.xml; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.security.oauth2.provider.expression.OAuth2WebSecurityExpressionHandler; import org.w3c.dom.Element; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class WebExpressionHandlerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/FormOAuth2AccessTokenMessageConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/FormOAuth2AccessTokenMessageConverter.java index 6d27ee68c..6bdf83f2f 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/FormOAuth2AccessTokenMessageConverter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/FormOAuth2AccessTokenMessageConverter.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -28,11 +28,15 @@ /** * Converter that can handle inbound form data and convert it to an access token. Needed to support external servers, * like Facebook that might not send JSON token data. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Rob Winch * @author Dave Syer * */ +@Deprecated public class FormOAuth2AccessTokenMessageConverter extends AbstractHttpMessageConverter { private final FormHttpMessageConverter delegateMessageConverter; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/FormOAuth2ExceptionHttpMessageConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/FormOAuth2ExceptionHttpMessageConverter.java index d66a13880..264a528d5 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/FormOAuth2ExceptionHttpMessageConverter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/FormOAuth2ExceptionHttpMessageConverter.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -31,11 +31,15 @@ /** * Converter that can handle inbound form data and convert it to an OAuth2 exception. Needed to support external servers, * like Facebook that might not send JSON data. - * -@author Rob Winch + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Rob Winch * @author Dave Syer * */ +@Deprecated public final class FormOAuth2ExceptionHttpMessageConverter implements HttpMessageConverter { private static final List SUPPORTED_MEDIA = Collections.singletonList(MediaType.APPLICATION_FORM_URLENCODED); diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/AbstractJaxbMessageConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/AbstractJaxbMessageConverter.java index 95887f438..6b9e8c418 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/AbstractJaxbMessageConverter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/AbstractJaxbMessageConverter.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -44,7 +44,6 @@ * @param The internal representation of the object that can be safely marshalled/unmarshalled using JAXB. * @param The external representation of the object that is exposed externally but cannot be marshalled/unmarshalled using JAXB. */ -@SuppressWarnings("restriction") abstract class AbstractJaxbMessageConverter extends AbstractXmlHttpMessageConverter { private final Class internalClass; @@ -85,7 +84,7 @@ protected final void writeToResult(E accessToken, HttpHeaders headers, Result re createMarshaller().marshal(convertedAccessToken, result); } catch (MarshalException ex) { - throw new HttpMessageNotWritableException("Could not marshal [" + accessToken + "]: " + ex.getMessage(), ex); + throw new HttpMessageNotWritableException("Could not marshal accessToken: " + ex.getMessage(), ex); } catch (JAXBException ex) { throw new HttpMessageConversionException("Could not instantiate JAXBContext: " + ex.getMessage(), ex); diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2AccessToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2AccessToken.java index 649fac68a..1df8ab8a0 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2AccessToken.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2AccessToken.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -18,7 +18,6 @@ import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; -@SuppressWarnings("restriction") @XmlRootElement(name = "oauth") class JaxbOAuth2AccessToken { private String accessToken; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2AccessTokenMessageConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2AccessTokenMessageConverter.java index 07d898077..ac4e45724 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2AccessTokenMessageConverter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2AccessTokenMessageConverter.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -19,6 +19,12 @@ import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + */ +@Deprecated public final class JaxbOAuth2AccessTokenMessageConverter extends AbstractJaxbMessageConverter { public JaxbOAuth2AccessTokenMessageConverter() { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2Exception.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2Exception.java index f0ecf9747..75dfa02bf 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2Exception.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2Exception.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -17,7 +17,6 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -@SuppressWarnings("restriction") @XmlRootElement(name = "oauth") @XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL) class JaxbOAuth2Exception { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2ExceptionMessageConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2ExceptionMessageConverter.java index f78313852..3b2f1146b 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2ExceptionMessageConverter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2ExceptionMessageConverter.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -14,6 +14,12 @@ import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + */ +@Deprecated public final class JaxbOAuth2ExceptionMessageConverter extends AbstractJaxbMessageConverter { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/AuthorizationRequest.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/AuthorizationRequest.java index 157e376f3..520f66960 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/AuthorizationRequest.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/AuthorizationRequest.java @@ -1,53 +1,304 @@ package org.springframework.security.oauth2.provider; +import java.io.Serializable; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.web.bind.annotation.SessionAttributes; /** - * Base class representing a request for authorization. There are convenience methods for the well-known properties - * required by the OAUth2 spec, and a set of generic authorizationParameters to allow for extensions. + * A request for authorization by an OAuth 2 Client, normally received and + * processed by the AuthorizationEndpoint. This class is meant to be manipulated + * throughout the authorization process, and is therefore treated as ephemeral + * and not to be stored long term. For long term storage, use the read-only + * {@link OAuth2Request} class. * + * HTTP request parameters are stored in the parameters map, and any processing + * the server makes throughout the lifecycle of a request are stored on + * individual properties. The original request parameters will remain available + * through the parameters map. For convenience, constants are defined in order + * to get at those original values. However, the parameters map is unmodifiable + * so that processing cannot drop the original values. + * + * This class is {@link Serializable} in order to support storage of the + * authorization request as a {@link SessionAttributes} member while the end + * user through the authorization process (which may span several page + * requests). + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer * @author Amanda Anganes */ -public interface AuthorizationRequest { +@SuppressWarnings("serial") +@Deprecated +public class AuthorizationRequest extends BaseRequest implements Serializable { + + /** + * Map to hold the original, unchanged parameter set submitted by a user to + * signal approval of the token grant approval. Once set this should not be + * modified. + */ + private Map approvalParameters = Collections.unmodifiableMap(new HashMap()); + + /** + * The value of the "state" parameter sent by the client in the request, if + * sent by the client. As this must be echoed back to the client unchanged, + * it should not be modified by any processing classes. + */ + private String state; + + /** + * Resolved requested response types initialized (by the + * OAuth2RequestFactory) with the response types originally requested. + */ + private Set responseTypes = new HashSet(); + + /** + * Resolved resource IDs. This set may change during request processing. + */ + private Set resourceIds = new HashSet(); + + /** + * Resolved granted authorities for this request. May change during request + * processing. + */ + private Collection authorities = new HashSet(); + + /** + * Whether the request has been approved by the end user (or other process). + * This will be altered by the User Approval Endpoint and/or the + * UserApprovalHandler as appropriate. + */ + private boolean approved = false; + + /** + * The resolved redirect URI of this request. A URI may be present in the + * original request, in the authorizationParameters, or it may not be + * provided, in which case it will be defaulted (by processing classes) to + * the Client's default registered value. + */ + private String redirectUri; + + /** + * Extension point for custom processing classes which may wish to store + * additional information about the OAuth2 request. Since this class will + * create a serializable OAuth2Request, all members of this extension map + * must be serializable. + */ + private Map extensions = new HashMap(); + + /** + * Default constructor. + */ + public AuthorizationRequest() { + } + + /** + * Full constructor. + */ + public AuthorizationRequest(Map authorizationParameters, Map approvalParameters, String clientId, Set scope, Set resourceIds, Collection authorities, boolean approved, String state, String redirectUri, + Set responseTypes) { + setClientId(clientId); + setRequestParameters(authorizationParameters); // in case we need to + // wrap the collection + setScope(scope); // in case we need to parse + if (resourceIds != null) { + this.resourceIds = new HashSet(resourceIds); + } + if (authorities != null) { + this.authorities = new HashSet(authorities); + } + this.approved = approved; + this.resourceIds = resourceIds; + this.redirectUri = redirectUri; + if (responseTypes != null) { + this.responseTypes = responseTypes; + } + this.state = state; + } + + public OAuth2Request createOAuth2Request() { + return new OAuth2Request(getRequestParameters(), getClientId(), getAuthorities(), isApproved(), getScope(), getResourceIds(), getRedirectUri(), getResponseTypes(), getExtensions()); + } + + /** + * Convenience constructor for unit tests, where client ID and scope are + * often the only needed fields. + * + * @param clientId + * @param scopes + */ + public AuthorizationRequest(String clientId, Collection scopes) { + setClientId(clientId); + setScope(scopes); // in case we need to parse + } + + /** + * Convenience method to set resourceIds and authorities on this request by + * inheriting from a ClientDetails object. + * + * @param clientDetails + */ + public void setResourceIdsAndAuthoritiesFromClientDetails(ClientDetails clientDetails) { + setResourceIds(clientDetails.getResourceIds()); + setAuthorities(clientDetails.getAuthorities()); + } + + public Map getApprovalParameters() { + return approvalParameters; + } + + public void setApprovalParameters(Map approvalParameters) { + this.approvalParameters = approvalParameters; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public Set getResponseTypes() { + return responseTypes; + } - public static final String CLIENT_ID = "client_id"; + public void setResponseTypes(Set responseTypes) { + this.responseTypes = responseTypes; + } - public static final String STATE = "state"; + public void setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + } - public static final String SCOPE = "scope"; + public void setApproved(boolean approved) { + this.approved = approved; + } - public static final String REDIRECT_URI = "redirect_uri"; + public void setAuthorities(Collection authorities) { + if (authorities != null) { + this.authorities = new HashSet(authorities); + } + } - public static final String RESPONSE_TYPE = "response_type"; + /** + * @return the extensions + */ + public Map getExtensions() { + return extensions; + } - public static final String USER_OAUTH_APPROVAL = "user_oauth_approval"; + public void setExtensions(Map extensions) { + this.extensions = extensions; + } - public Map getAuthorizationParameters(); - - public Map getApprovalParameters(); + public void setResourceIds(Set resourceIds) { + this.resourceIds = resourceIds; + } - public String getClientId(); + public void setClientId(String clientId) { + super.setClientId(clientId); + } - public Set getScope(); + /** + * Set the scope value. If the collection contains only a single scope + * value, this method will parse that value into a collection using + * {@link OAuth2Utils#parseParameterList}. + * + * @see TokenRequest#setScope + * + * @param scope + */ + public void setScope(Collection scope) { + super.setScope(scope); + } - public Set getResourceIds(); + /** + * Set the Request Parameters on this authorization request, which represent + * the original request parameters and should never be changed during + * processing. The map passed in is wrapped in an unmodifiable map instance. + * + * @see TokenRequest#setRequestParameters + * + * @param requestParameters + */ + public void setRequestParameters(Map requestParameters) { + super.setRequestParameters(requestParameters); + } - public Collection getAuthorities(); + /** + * @return the resourceIds + */ + public Set getResourceIds() { + return resourceIds; + } - public boolean isApproved(); + /** + * @return the authorities + */ + public Collection getAuthorities() { + return authorities; + } - public boolean isDenied(); + /** + * @return the approved + */ + public boolean isApproved() { + return approved; + } - public String getState(); + /** + * @return the redirectUri + */ + public String getRedirectUri() { + return redirectUri; + } - public String getRedirectUri(); + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((approvalParameters == null) ? 0 : approvalParameters.hashCode()); + result = prime * result + ((responseTypes == null) ? 0 : responseTypes.hashCode()); + result = prime * result + ((state == null) ? 0 : state.hashCode()); + return result; + } - public Set getResponseTypes(); + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + AuthorizationRequest other = (AuthorizationRequest) obj; + if (approvalParameters == null) { + if (other.approvalParameters != null) + return false; + } else if (!approvalParameters.equals(other.approvalParameters)) + return false; + if (responseTypes == null) { + if (other.responseTypes != null) + return false; + } else if (!responseTypes.equals(other.responseTypes)) + return false; + if (state == null) { + if (other.state != null) + return false; + } else if (!state.equals(other.state)) + return false; + return true; + } } \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/AuthorizationRequestManager.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/AuthorizationRequestManager.java deleted file mode 100644 index 48c61a71e..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/AuthorizationRequestManager.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2006-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package org.springframework.security.oauth2.provider; - -import java.util.Map; - -import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint; -import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint; - -/** - * Strategy for managing AuthorizationRequest instances during a token grant. - * - * @author Dave Syer - * - */ -public interface AuthorizationRequestManager { - - /** - * Create a new {@link AuthorizationRequest} extracting all the needed information from the incoming parameter map. - * Typical implementations would load the client details from the client id provided and validate the grant type and - * scopes, populating any fields in the request that are known only to the authorization server. - * - * @param authorizationParameters the parameters in the request - * @return a new AuthorizationRequest - */ - AuthorizationRequest createAuthorizationRequest(Map authorizationParameters); - - /** - *

- * Validate the parameters provided by the client. Called by the {@link AuthorizationEndpoint} and also by the - * {@link TokenEndpoint} before a response is sent back to the client. Note that during an authorization code flow - * both endpoints will call this method, but the TokenEndpoint in that case has very little if anything to validate - * since all the parameters neeeded for the access token were provided to the AuthorizationEndpoint. - *

- * - *

- * Implementations should at a minimum check that the scope values requested are legal for the client. - *

- * - * @param parameters the request parameters - * @param clientDetails the client requesting the token - */ - void validateParameters(Map parameters, ClientDetails clientDetails); - -} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/BaseClientDetails.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/BaseClientDetails.java deleted file mode 100644 index 07cbcd4c3..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/BaseClientDetails.java +++ /dev/null @@ -1,365 +0,0 @@ -package org.springframework.security.oauth2.provider; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonProcessingException; -import org.codehaus.jackson.JsonToken; -import org.codehaus.jackson.annotate.JsonAnyGetter; -import org.codehaus.jackson.annotate.JsonAnySetter; -import org.codehaus.jackson.annotate.JsonIgnore; -import org.codehaus.jackson.annotate.JsonIgnoreProperties; -import org.codehaus.jackson.annotate.JsonProperty; -import org.codehaus.jackson.map.DeserializationContext; -import org.codehaus.jackson.map.annotate.JsonDeserialize; -import org.codehaus.jackson.map.annotate.JsonSerialize; -import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion; -import org.codehaus.jackson.map.deser.std.StdDeserializer; -import org.codehaus.jackson.map.type.SimpleType; -import org.codehaus.jackson.type.JavaType; -import org.codehaus.jackson.type.TypeReference; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.util.StringUtils; - -/** - * Base implementation of {@link org.springframework.security.oauth2.provider.ClientDetails}. - * - * @author Ryan Heaton - * @author Dave Syer - */ -@JsonSerialize(include = Inclusion.NON_DEFAULT) -@JsonIgnoreProperties(ignoreUnknown = true) -public class BaseClientDetails implements ClientDetails { - - @JsonProperty("client_id") - private String clientId; - - @JsonProperty("client_secret") - private String clientSecret; - - @JsonDeserialize(using = ArrayOrStringDeserializer.class) - private Set scope = Collections.emptySet(); - - @JsonProperty("resource_ids") - @JsonDeserialize(using = ArrayOrStringDeserializer.class) - private Set resourceIds = Collections.emptySet(); - - @JsonProperty("authorized_grant_types") - @JsonDeserialize(using = ArrayOrStringDeserializer.class) - private Set authorizedGrantTypes = Collections.emptySet(); - - @JsonProperty("redirect_uri") - @JsonDeserialize(using = ArrayOrStringDeserializer.class) - private Set registeredRedirectUris; - - private List authorities = Collections.emptyList(); - - @JsonProperty("access_token_validity") - private Integer accessTokenValiditySeconds; - - @JsonProperty("refresh_token_validity") - private Integer refreshTokenValiditySeconds; - - @JsonIgnore - private Map additionalInformation = new LinkedHashMap(); - - public BaseClientDetails() { - } - - public BaseClientDetails(ClientDetails prototype) { - this(); - setAccessTokenValiditySeconds(prototype.getAccessTokenValiditySeconds()); - setRefreshTokenValiditySeconds(prototype.getRefreshTokenValiditySeconds()); - setAuthorities(prototype.getAuthorities()); - setAuthorizedGrantTypes(prototype.getAuthorizedGrantTypes()); - setClientId(prototype.getClientId()); - setClientSecret(prototype.getClientSecret()); - setRegisteredRedirectUri(prototype.getRegisteredRedirectUri()); - setScope(prototype.getScope()); - setResourceIds(prototype.getResourceIds()); - } - - public BaseClientDetails(String clientId, String resourceIds, String scopes, String grantTypes, String authorities) { - this(clientId, resourceIds, scopes, grantTypes, authorities, null); - } - - public BaseClientDetails(String clientId, String resourceIds, String scopes, String grantTypes, String authorities, - String redirectUris) { - - this.clientId = clientId; - - if (StringUtils.hasText(resourceIds)) { - Set resources = StringUtils.commaDelimitedListToSet(resourceIds); - if (!resources.isEmpty()) { - this.resourceIds = resources; - } - } - - if (StringUtils.hasText(scopes)) { - Set scopeList = StringUtils.commaDelimitedListToSet(scopes); - if (!scopeList.isEmpty()) { - this.scope = scopeList; - } - } - - if (StringUtils.hasText(grantTypes)) { - this.authorizedGrantTypes = StringUtils.commaDelimitedListToSet(grantTypes); - } - else { - this.authorizedGrantTypes = new HashSet(Arrays.asList("authorization_code", "refresh_token")); - } - - if (StringUtils.hasText(authorities)) { - this.authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(authorities); - } - - if (StringUtils.hasText(redirectUris)) { - this.registeredRedirectUris = StringUtils.commaDelimitedListToSet(redirectUris); - } - } - - @JsonIgnore - public String getClientId() { - return clientId; - } - - public void setClientId(String clientId) { - this.clientId = clientId; - } - - @JsonIgnore - public boolean isSecretRequired() { - return this.clientSecret != null; - } - - @JsonIgnore - public String getClientSecret() { - return clientSecret; - } - - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; - } - - @JsonIgnore - public boolean isScoped() { - return this.scope != null && !this.scope.isEmpty(); - } - - public Set getScope() { - return scope; - } - - public void setScope(Collection scope) { - this.scope = scope == null ? Collections. emptySet() : new LinkedHashSet(scope); - } - - @JsonIgnore - public Set getResourceIds() { - return resourceIds; - } - - public void setResourceIds(Collection resourceIds) { - this.resourceIds = resourceIds == null ? Collections. emptySet() : new LinkedHashSet( - resourceIds); - } - - @JsonIgnore - public Set getAuthorizedGrantTypes() { - return authorizedGrantTypes; - } - - public void setAuthorizedGrantTypes(Collection authorizedGrantTypes) { - this.authorizedGrantTypes = new LinkedHashSet(authorizedGrantTypes); - } - - @JsonIgnore - public Set getRegisteredRedirectUri() { - return registeredRedirectUris; - } - - public void setRegisteredRedirectUri(Set registeredRedirectUris) { - this.registeredRedirectUris = registeredRedirectUris == null ? null : new LinkedHashSet( - registeredRedirectUris); - } - - @JsonProperty("authorities") - private List getAuthoritiesAsStrings() { - return new ArrayList(AuthorityUtils.authorityListToSet(authorities)); - } - - @JsonProperty("authorities") - @JsonDeserialize(using = ArrayOrStringDeserializer.class) - private void setAuthoritiesAsStrings(Set values) { - setAuthorities(AuthorityUtils.createAuthorityList(values.toArray(new String[values.size()]))); - } - - public static class ArrayOrStringDeserializer extends StdDeserializer> { - - public ArrayOrStringDeserializer() { - super(Set.class); - } - - @Override - public JavaType getValueType() { - return SimpleType.construct(String.class); - } - - @Override - public Set deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, - JsonProcessingException { - JsonToken token = jp.getCurrentToken(); - if (token.isScalarValue()) { - String list = jp.getText(); - list = list.replaceAll("\\s+", ","); - return new LinkedHashSet(Arrays.asList(StringUtils.commaDelimitedListToStringArray(list))); - } - return jp.readValueAs(new TypeReference>() { - }); - } - } - - @JsonIgnore - public Collection getAuthorities() { - return authorities; - } - - @JsonIgnore - public void setAuthorities(Collection authorities) { - this.authorities = new ArrayList(authorities); - } - - @JsonIgnore - public Integer getAccessTokenValiditySeconds() { - return accessTokenValiditySeconds; - } - - public void setAccessTokenValiditySeconds(Integer accessTokenValiditySeconds) { - this.accessTokenValiditySeconds = accessTokenValiditySeconds; - } - - @JsonIgnore - public Integer getRefreshTokenValiditySeconds() { - return refreshTokenValiditySeconds; - } - - public void setRefreshTokenValiditySeconds(Integer refreshTokenValiditySeconds) { - this.refreshTokenValiditySeconds = refreshTokenValiditySeconds; - } - - public void setAdditionalInformation(Map additionalInformation) { - this.additionalInformation = new LinkedHashMap(additionalInformation); - } - - @JsonAnyGetter - public Map getAdditionalInformation() { - return Collections.unmodifiableMap(this.additionalInformation); - } - - @JsonAnySetter - public void addAdditionalInformation(String key, Object value) { - this.additionalInformation.put(key, value); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((accessTokenValiditySeconds==null) ? 0 : accessTokenValiditySeconds); - result = prime * result + ((refreshTokenValiditySeconds == null) ? 0 : refreshTokenValiditySeconds); - result = prime * result + ((authorities == null) ? 0 : authorities.hashCode()); - result = prime * result + ((authorizedGrantTypes == null) ? 0 : authorizedGrantTypes.hashCode()); - result = prime * result + ((clientId == null) ? 0 : clientId.hashCode()); - result = prime * result + ((clientSecret == null) ? 0 : clientSecret.hashCode()); - result = prime * result + ((registeredRedirectUris == null) ? 0 : registeredRedirectUris.hashCode()); - result = prime * result + ((resourceIds == null) ? 0 : resourceIds.hashCode()); - result = prime * result + ((scope == null) ? 0 : scope.hashCode()); - result = prime * result + additionalInformation.hashCode(); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - BaseClientDetails other = (BaseClientDetails) obj; - if (accessTokenValiditySeconds != other.accessTokenValiditySeconds) - return false; - if (refreshTokenValiditySeconds != other.refreshTokenValiditySeconds) - return false; - if (authorities == null) { - if (other.authorities != null) - return false; - } - else if (!authorities.equals(other.authorities)) - return false; - if (authorizedGrantTypes == null) { - if (other.authorizedGrantTypes != null) - return false; - } - else if (!authorizedGrantTypes.equals(other.authorizedGrantTypes)) - return false; - if (clientId == null) { - if (other.clientId != null) - return false; - } - else if (!clientId.equals(other.clientId)) - return false; - if (clientSecret == null) { - if (other.clientSecret != null) - return false; - } - else if (!clientSecret.equals(other.clientSecret)) - return false; - if (registeredRedirectUris == null) { - if (other.registeredRedirectUris != null) - return false; - } - else if (!registeredRedirectUris.equals(other.registeredRedirectUris)) - return false; - if (resourceIds == null) { - if (other.resourceIds != null) - return false; - } - else if (!resourceIds.equals(other.resourceIds)) - return false; - if (scope == null) { - if (other.scope != null) - return false; - } - else if (!scope.equals(other.scope)) - return false; - if (additionalInformation == null) { - if (other.additionalInformation != null) - return false; - } - else if (!additionalInformation.equals(other.additionalInformation)) - return false; - return true; - } - - @Override - public String toString() { - return "BaseClientDetails [clientId=" + clientId + ", clientSecret=" + clientSecret + ", scope=" + scope - + ", resourceIds=" + resourceIds + ", authorizedGrantTypes=" + authorizedGrantTypes - + ", registeredRedirectUris=" + registeredRedirectUris + ", authorities=" + authorities - + ", accessTokenValiditySeconds=" + accessTokenValiditySeconds + ", refreshTokenValiditySeconds=" - + refreshTokenValiditySeconds + ", additionalInformation=" + additionalInformation + "]"; - } - -} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/BaseRequest.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/BaseRequest.java new file mode 100644 index 000000000..47e869cca --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/BaseRequest.java @@ -0,0 +1,162 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.security.oauth2.common.util.OAuth2Utils; + +/** + * + * A base class for the three "*Request" classes used in processing OAuth 2 + * authorizations. This class should never be used directly, + * and it should never be used as the type for a local or other + * variable. + * + * @author Dave Syer + * + */ +@SuppressWarnings("serial") +abstract class BaseRequest implements Serializable { + + /** + * Resolved client ID. This may be present in the original request + * parameters, or in some cases may be inferred by a processing class and + * inserted here. + */ + private String clientId; + + /** + * Resolved scope set, initialized (by the OAuth2RequestFactory) with the + * scopes originally requested. Further processing and user interaction may + * alter the set of scopes that is finally granted and stored when the + * request processing is complete. + */ + private Set scope = new HashSet(); + + /** + * Map of parameters passed in to the Authorization Endpoint or Token + * Endpoint, preserved unchanged from the original request. This map should + * not be modified after initialization. In general, classes should not + * retrieve values from this map directly, and should instead use the + * individual members on this class. + * + * The OAuth2RequestFactory is responsible for initializing all members of + * this class, usually by parsing the values inside the requestParmaeters + * map. + * + */ + private Map requestParameters = Collections + .unmodifiableMap(new HashMap()); + + public String getClientId() { + return clientId; + } + + public Set getScope() { + return scope; + } + + /** + * Warning: most clients should use the individual properties of this class, + * such as {{@link #getScope()} or { {@link #getClientId()}, rather than + * retrieving values from this map. + * + * @return the original, unchanged set of request parameters + */ + public Map getRequestParameters() { + return requestParameters; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((clientId == null) ? 0 : clientId.hashCode()); + result = prime + * result + + ((requestParameters == null) ? 0 : requestParameters + .hashCode()); + result = prime * result + ((scope == null) ? 0 : scope.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BaseRequest other = (BaseRequest) obj; + if (clientId == null) { + if (other.clientId != null) + return false; + } else if (!clientId.equals(other.clientId)) + return false; + if (requestParameters == null) { + if (other.requestParameters != null) + return false; + } else if (!requestParameters.equals(other.requestParameters)) + return false; + if (scope == null) { + if (other.scope != null) + return false; + } else if (!scope.equals(other.scope)) + return false; + return true; + } + + protected void setScope(Collection scope) { + if (scope != null && scope.size() == 1) { + String value = scope.iterator().next(); + /* + * This is really an error, but it can catch out unsuspecting users + * and it's easy to fix. It happens when an AuthorizationRequest + * gets bound accidentally from request parameters using + * @ModelAttribute. + */ + if (value.contains(" ") || value.contains(",")) { + scope = OAuth2Utils.parseParameterList(value); + } + } + this.scope = Collections + .unmodifiableSet(scope == null ? new LinkedHashSet() + : new LinkedHashSet(scope)); + } + + protected void setRequestParameters(Map requestParameters) { + if (requestParameters != null) { + this.requestParameters = Collections + .unmodifiableMap(new HashMap(requestParameters)); + } + } + + protected void setClientId(String clientId) { + this.clientId = clientId; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientAlreadyExistsException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientAlreadyExistsException.java index 5a7ba4a40..2459ccd2d 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientAlreadyExistsException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientAlreadyExistsException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -17,10 +17,15 @@ /** * Exception indicating that a client registration already exists (e.g. if someone tries to create a duplicate). - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@SuppressWarnings("serial") +@Deprecated public class ClientAlreadyExistsException extends ClientRegistrationException { public ClientAlreadyExistsException(String msg) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientDetails.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientDetails.java index e9e6c7616..5aacd8917 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientDetails.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientDetails.java @@ -9,9 +9,13 @@ /** * Client details for OAuth 2 - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@Deprecated public interface ClientDetails extends Serializable { /** @@ -73,11 +77,11 @@ public interface ClientDetails extends Serializable { Set getRegisteredRedirectUri(); /** - * Get the authorities that are granted to the OAuth client. Note that these are NOT the authorities that are - * granted to the user with an authorized access token. Instead, these authorities are inherent to the client - * itself. + * Returns the authorities that are granted to the OAuth client. Cannot return null. + * Note that these are NOT the authorities that are granted to the user with an authorized access token. + * Instead, these authorities are inherent to the client itself. * - * @return The authorities. + * @return the authorities (never null) */ Collection getAuthorities(); @@ -90,14 +94,23 @@ public interface ClientDetails extends Serializable { Integer getAccessTokenValiditySeconds(); /** - * The refresh token validity period for this client. Zero or negative for default value set by token service. + * The refresh token validity period for this client. Null for default value set by token service, and + * zero or negative for non-expiring tokens. * * @return the refresh token validity period */ Integer getRefreshTokenValiditySeconds(); + + /** + * Test whether client needs user approval for a particular scope. + * + * @param scope the scope to consider + * @return true if this client does not need user approval + */ + boolean isAutoApprove(String scope); /** - * Additional information for this client, not neeed by the vanilla OAuth protocol but might be useful, for example, + * Additional information for this client, not needed by the vanilla OAuth protocol but might be useful, for example, * for storing descriptive information. * * @return a map of additional information diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientDetailsService.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientDetailsService.java index 93730bea7..2137fe459 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientDetailsService.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientDetailsService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,22 +16,25 @@ package org.springframework.security.oauth2.provider; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; /** - * A service that provides the details about an oauth 2client. + * A service that provides the details about an OAuth2 client. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. * * @author Ryan Heaton */ +@Deprecated public interface ClientDetailsService { /** - * Load a client by the client id. This method must NOT return null. + * Load a client by the client id. This method must not return null. * * @param clientId The client id. - * @return The client details. - * @throws OAuth2Exception If the client account is locked, expired, disabled, or for any other reason. + * @return The client details (never null). + * @throws ClientRegistrationException If the client account is locked, expired, disabled, or invalid for any other reason. */ - ClientDetails loadClientByClientId(String clientId) throws OAuth2Exception; + ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException; } \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientRegistrationException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientRegistrationException.java index 05c108b47..29d1b6975 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientRegistrationException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientRegistrationException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,9 +16,14 @@ package org.springframework.security.oauth2.provider; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@SuppressWarnings("serial") +@Deprecated public class ClientRegistrationException extends RuntimeException { public ClientRegistrationException(String msg) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientRegistrationService.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientRegistrationService.java index 4515539cf..d032db2c4 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientRegistrationService.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/ClientRegistrationService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -20,10 +20,14 @@ /** * Interface for client registration, handling add, update and remove of {@link ClientDetails} from an Authorization * Server. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface ClientRegistrationService { void addClientDetails(ClientDetails clientDetails) throws ClientAlreadyExistsException; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/CompositeTokenGranter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/CompositeTokenGranter.java index b820f4b48..a880d581d 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/CompositeTokenGranter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/CompositeTokenGranter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -22,9 +22,13 @@ import org.springframework.security.oauth2.common.OAuth2AccessToken; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class CompositeTokenGranter implements TokenGranter { private final List tokenGranters; @@ -33,14 +37,21 @@ public CompositeTokenGranter(List tokenGranters) { this.tokenGranters = new ArrayList(tokenGranters); } - public OAuth2AccessToken grant(String grantType, AuthorizationRequest authorizationRequest) { + public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { for (TokenGranter granter : tokenGranters) { - OAuth2AccessToken grant = granter.grant(grantType, authorizationRequest); + OAuth2AccessToken grant = granter.grant(grantType, tokenRequest); if (grant!=null) { return grant; } } return null; } + + public void addTokenGranter(TokenGranter tokenGranter) { + if (tokenGranter == null) { + throw new IllegalArgumentException("Token granter is null"); + } + tokenGranters.add(tokenGranter); + } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/DefaultAuthorizationRequest.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/DefaultAuthorizationRequest.java deleted file mode 100644 index fdc0a36f2..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/DefaultAuthorizationRequest.java +++ /dev/null @@ -1,238 +0,0 @@ -package org.springframework.security.oauth2.provider; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.common.util.OAuth2Utils; -import org.springframework.util.StringUtils; - -/** - * Base class representing a request for authorization. There are convenience methods for the well-known properties - * required by the OAuth2 spec, and a set of generic authorizationParameters to allow for extensions. - * - * @author Ryan Heaton - * @author Dave Syer - * @author Amanda Anganes - */ -public class DefaultAuthorizationRequest implements AuthorizationRequest, Serializable { - - private Set scope = new LinkedHashSet(); - - private Set resourceIds = new HashSet(); - - private boolean approved = false; - - private Collection authorities = new HashSet(); - - private Map authorizationParameters = new HashMap(); - - private Map approvalParameters = new HashMap(); - - private String resolvedRedirectUri; - - public DefaultAuthorizationRequest(Map authorizationParameters) { - this(authorizationParameters, Collections. emptyMap(), authorizationParameters.get(CLIENT_ID), - OAuth2Utils.parseParameterList(authorizationParameters.get("scope")), null, null, false); - } - - public DefaultAuthorizationRequest(Map authorizationParameters, - Map approvalParameters, String clientId, Collection scope) { - this(authorizationParameters, approvalParameters, clientId, scope, null, null, false); - } - - public DefaultAuthorizationRequest(String clientId, Collection scope) { - this(null, null, clientId, scope, null, null, false); - } - - public DefaultAuthorizationRequest(AuthorizationRequest copy) { - this(copy.getAuthorizationParameters(), copy.getApprovalParameters(), copy.getClientId(), copy.getScope(), copy - .getAuthorities(), copy.getResourceIds(), copy.isApproved()); - setRedirectUri(copy.getRedirectUri()); - if (!scope.isEmpty()) { - this.authorizationParameters.put(SCOPE, OAuth2Utils.formatParameterList(scope)); - } - } - - private DefaultAuthorizationRequest(Map authorizationParameters, - Map approvalParameters, String clientId, Collection scope, - Collection authorities, Collection resourceIds, boolean approved) { - if (authorizationParameters != null) { - this.authorizationParameters.putAll(authorizationParameters); - } - if (approvalParameters != null) { - this.approvalParameters.putAll(approvalParameters); - } - if (resourceIds != null) { - this.resourceIds = new HashSet(resourceIds); - } - if (scope != null) { - this.scope = new LinkedHashSet(scope); - } - if (authorities != null) { - this.authorities = new HashSet(authorities); - } - this.authorizationParameters.put(CLIENT_ID, clientId); - this.authorizationParameters.put(SCOPE, OAuth2Utils.formatParameterList(scope)); - this.approved = approved; - } - - public Map getAuthorizationParameters() { - return Collections.unmodifiableMap(authorizationParameters); - } - - public Map getApprovalParameters() { - return Collections.unmodifiableMap(approvalParameters); - } - - public String getClientId() { - return authorizationParameters.get(CLIENT_ID); - } - - public Set getScope() { - return Collections.unmodifiableSet(this.scope); - } - - public Set getResourceIds() { - return Collections.unmodifiableSet(resourceIds); - } - - public Collection getAuthorities() { - return Collections.unmodifiableSet((Set) authorities); - } - - public boolean isApproved() { - return approved; - } - - public boolean isDenied() { - return !approved; - } - - public String getState() { - return authorizationParameters.get(STATE); - } - - public String getRedirectUri() { - return resolvedRedirectUri == null ? authorizationParameters.get(REDIRECT_URI) : resolvedRedirectUri; - } - - public Set getResponseTypes() { - return OAuth2Utils.parseParameterList(authorizationParameters.get(RESPONSE_TYPE)); - } - - public void setRedirectUri(String redirectUri) { - this.resolvedRedirectUri = redirectUri; - } - - public void addClientDetails(ClientDetails clientDetails) { - resourceIds.addAll(clientDetails.getResourceIds()); - authorities.addAll(clientDetails.getAuthorities()); - } - - public void setScope(Set scope) { - if (scope != null && scope.size() == 1) { - String value = scope.iterator().next(); - /* - * This is really an error, but it can catch out unsuspecting users and it's easy to fix. It happens when an - * AuthorizationRequest gets bound accidentally from request parameters using @ModelAttribute. - */ - if (value.contains(" ") || scope.contains(",")) { - scope = OAuth2Utils.parseParameterList(value); - } - } - this.scope = scope == null ? new LinkedHashSet() : new LinkedHashSet(scope); - authorizationParameters.put(SCOPE, OAuth2Utils.formatParameterList(scope)); - } - - public void setResourceIds(Set resourceIds) { - this.resourceIds = resourceIds == null ? new HashSet() : new HashSet(resourceIds); - } - - public void setApproved(boolean approved) { - this.approved = approved; - } - - public void setAuthorities(Collection authorities) { - this.authorities = authorities == null ? new HashSet() : new HashSet( - authorities); - } - - public void setAuthorizationParameters(Map authorizationParameters) { - this.authorizationParameters = authorizationParameters == null ? new HashMap() - : new HashMap(authorizationParameters); - if (authorizationParameters.containsKey(SCOPE) && StringUtils.hasText(authorizationParameters.get(SCOPE))) { - String scope = authorizationParameters.get(SCOPE); - setScope(OAuth2Utils.parseParameterList(scope)); - } - } - - public void setApprovalParameters(Map approvalParameters) { - this.approvalParameters = approvalParameters == null ? new HashMap() - : new HashMap(approvalParameters); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((authorities == null) ? 0 : authorities.hashCode()); - result = prime * result + (approved ? 1231 : 1237); - result = prime * result + ((authorizationParameters == null) ? 0 : authorizationParameters.hashCode()); - result = prime * result + ((approvalParameters == null) ? 0 : approvalParameters.hashCode()); - result = prime * result + ((resourceIds == null) ? 0 : resourceIds.hashCode()); - result = prime * result + ((scope == null) ? 0 : scope.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - DefaultAuthorizationRequest other = (DefaultAuthorizationRequest) obj; - if (authorities == null) { - if (other.authorities != null) - return false; - } - else if (!authorities.equals(other.authorities)) - return false; - if (approved != other.approved) - return false; - if (authorizationParameters == null) { - if (other.authorizationParameters != null) - return false; - } - else if (!authorizationParameters.equals(other.authorizationParameters)) - return false; - if (resourceIds == null) { - if (other.resourceIds != null) - return false; - } - else if (!resourceIds.equals(other.resourceIds)) - return false; - if (scope == null) { - if (other.scope != null) - return false; - } - else if (!scope.equals(other.scope)) - return false; - if (approvalParameters == null) { - if (other.approvalParameters != null) - return false; - } - else if (!approvalParameters.equals(other.approvalParameters)) - return false; - return true; - } - -} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/DefaultAuthorizationRequestManager.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/DefaultAuthorizationRequestManager.java deleted file mode 100644 index f1ffede72..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/DefaultAuthorizationRequestManager.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2006-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package org.springframework.security.oauth2.provider; - -import java.util.Collections; -import java.util.Map; -import java.util.Set; - -import org.springframework.security.oauth2.common.exceptions.InvalidClientException; -import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; -import org.springframework.security.oauth2.common.util.OAuth2Utils; - -/** - * Default implementation of {@link AuthorizationRequestManager} which validates grant types and scopes and fills in - * scopes with the default values from the client if they are missing. - * - * @author Dave Syer - * - */ -public class DefaultAuthorizationRequestManager implements AuthorizationRequestManager { - - private final ClientDetailsService clientDetailsService; - - public DefaultAuthorizationRequestManager(ClientDetailsService clientDetailsService) { - this.clientDetailsService = clientDetailsService; - } - - public AuthorizationRequest createAuthorizationRequest(Map parameters) { - - String clientId = parameters.get("client_id"); - if (clientId == null) { - throw new InvalidClientException("A client id must be provided"); - } - ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId); - Set scopes = OAuth2Utils.parseParameterList(parameters.get("scope")); - if ((scopes == null || scopes.isEmpty())) { - // If no scopes are specified in the incoming data, use the default values registered with the client - // (the spec allows us to choose between this option and rejecting the request completely, so we'll take the - // least obnoxious choice as a default). - scopes = clientDetails.getScope(); - } - DefaultAuthorizationRequest request = new DefaultAuthorizationRequest(parameters, Collections. emptyMap(), - clientId, scopes); - request.addClientDetails(clientDetails); - return request; - - } - - public void validateParameters(Map parameters, ClientDetails clientDetails) { - if (parameters.containsKey("scope")) { - if (clientDetails.isScoped()) { - Set validScope = clientDetails.getScope(); - for (String scope : OAuth2Utils.parseParameterList(parameters.get("scope"))) { - if (!validScope.contains(scope)) { - throw new InvalidScopeException("Invalid scope: " + scope, validScope); - } - } - } - } - } - -} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/DefaultSecurityContextAccessor.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/DefaultSecurityContextAccessor.java new file mode 100644 index 000000000..deb892923 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/DefaultSecurityContextAccessor.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013-2014 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.provider; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + * Strategy for accessing useful information about the current security context. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class DefaultSecurityContextAccessor implements SecurityContextAccessor { + + @Override + public boolean isUser() { + Authentication authentication = getUserAuthentication(); + return authentication != null; + } + + @Override + public Set getAuthorities() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + return Collections.emptySet(); + } + return Collections.unmodifiableSet(new HashSet(authentication.getAuthorities())); + } + + private Authentication getUserAuthentication() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + return null; + } + if (authentication instanceof OAuth2Authentication) { + OAuth2Authentication oauth = (OAuth2Authentication) authentication; + return oauth.getUserAuthentication(); + } + return authentication; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/NoSuchClientException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/NoSuchClientException.java index 5089ab29a..3712afaeb 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/NoSuchClientException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/NoSuchClientException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,9 +16,14 @@ package org.springframework.security.oauth2.provider; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@SuppressWarnings("serial") +@Deprecated public class NoSuchClientException extends ClientRegistrationException { public NoSuchClientException(String msg) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/OAuth2Authentication.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/OAuth2Authentication.java index dfad67932..d4543c853 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/OAuth2Authentication.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/OAuth2Authentication.java @@ -2,18 +2,23 @@ import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.CredentialsContainer; /** * An OAuth 2 authentication token can contain two authentications: one for the client and one for the user. Since some * OAuth authorization grants don't require user authentication, the user authentication may be null. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@Deprecated public class OAuth2Authentication extends AbstractAuthenticationToken { private static final long serialVersionUID = -4809832298438307309L; - private final AuthorizationRequest clientAuthentication; + private final OAuth2Request storedRequest; private final Authentication userAuthentication; @@ -21,12 +26,12 @@ public class OAuth2Authentication extends AbstractAuthenticationToken { * Construct an OAuth 2 authentication. Since some grant types don't require user authentication, the user * authentication may be null. * - * @param authorizationRequest The authorization request (must not be null). + * @param storedRequest The authorization request (must not be null). * @param userAuthentication The user authentication (possibly null). */ - public OAuth2Authentication(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { - super(userAuthentication == null ? authorizationRequest.getAuthorities() : userAuthentication.getAuthorities()); - this.clientAuthentication = authorizationRequest; + public OAuth2Authentication(OAuth2Request storedRequest, Authentication userAuthentication) { + super(userAuthentication == null ? storedRequest.getAuthorities() : userAuthentication.getAuthorities()); + this.storedRequest = storedRequest; this.userAuthentication = userAuthentication; } @@ -35,7 +40,7 @@ public Object getCredentials() { } public Object getPrincipal() { - return this.userAuthentication == null ? this.clientAuthentication.getClientId() : this.userAuthentication + return this.userAuthentication == null ? this.storedRequest.getClientId() : this.userAuthentication .getPrincipal(); } @@ -53,8 +58,8 @@ public boolean isClientOnly() { * * @return The client authentication. */ - public AuthorizationRequest getAuthorizationRequest() { - return clientAuthentication; + public OAuth2Request getOAuth2Request() { + return storedRequest; } /** @@ -68,10 +73,18 @@ public Authentication getUserAuthentication() { @Override public boolean isAuthenticated() { - return this.clientAuthentication.isApproved() + return this.storedRequest.isApproved() && (this.userAuthentication == null || this.userAuthentication.isAuthenticated()); } + @Override + public void eraseCredentials() { + super.eraseCredentials(); + if (this.userAuthentication != null && CredentialsContainer.class.isAssignableFrom(this.userAuthentication.getClass())) { + CredentialsContainer.class.cast(this.userAuthentication).eraseCredentials(); + } + } + @Override public boolean equals(Object o) { if (this == o) { @@ -86,13 +99,17 @@ public boolean equals(Object o) { OAuth2Authentication that = (OAuth2Authentication) o; - if (!clientAuthentication.equals(that.clientAuthentication)) { + if (!storedRequest.equals(that.storedRequest)) { return false; } if (userAuthentication != null ? !userAuthentication.equals(that.userAuthentication) : that.userAuthentication != null) { return false; } + + if (getDetails()!=null ? !getDetails().equals(that.getDetails()) : that.getDetails()!=null) { + // return false; + } return true; } @@ -100,7 +117,7 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = super.hashCode(); - result = 31 * result + clientAuthentication.hashCode(); + result = 31 * result + storedRequest.hashCode(); result = 31 * result + (userAuthentication != null ? userAuthentication.hashCode() : 0); return result; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/OAuth2Request.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/OAuth2Request.java new file mode 100644 index 000000000..a3d2fbae3 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/OAuth2Request.java @@ -0,0 +1,255 @@ +package org.springframework.security.oauth2.provider; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.common.util.OAuth2Utils; + +/** + * Represents a stored authorization or token request. Used as part of the OAuth2Authentication object to store a + * request's authentication information. Does not expose public setters so that clients can not mutate state if they + * respect the declared type of the request. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Amanda Anganes + * @author Dave Syer + * + */ +@Deprecated +public class OAuth2Request extends BaseRequest implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Resolved resource IDs. This set may change during request processing. + */ + private Set resourceIds = new HashSet(); + + /** + * Resolved granted authorities for this request. May change during request processing. + */ + private Collection authorities = new HashSet(); + + /** + * Whether the request has been approved by the end user (or other process). This will be altered by the User + * Approval Endpoint and/or the UserApprovalHandler as appropriate. + */ + private boolean approved = false; + + /** + * Will be non-null if the request is for a token to be refreshed (the original grant type might still be available + * via {@link #getGrantType()}). + */ + private TokenRequest refresh = null; + + /** + * The resolved redirect URI of this request. A URI may be present in the original request, in the + * authorizationParameters, or it may not be provided, in which case it will be defaulted (by processing classes) to + * the Client's default registered value. + */ + private String redirectUri; + + /** + * Resolved requested response types initialized (by the OAuth2RequestFactory) with the response types originally + * requested. + */ + private Set responseTypes = new HashSet(); + + /** + * Extension point for custom processing classes which may wish to store additional information about the OAuth2 + * request. Since this class is serializable, all members of this map must also be serializable. + */ + private Map extensions = new HashMap(); + + public OAuth2Request(Map requestParameters, String clientId, + Collection authorities, boolean approved, Set scope, + Set resourceIds, String redirectUri, Set responseTypes, + Map extensionProperties) { + setClientId(clientId); + setRequestParameters(requestParameters); + setScope(scope); + if (resourceIds != null) { + this.resourceIds = new HashSet(resourceIds); + } + if (authorities != null) { + this.authorities = new HashSet(authorities); + } + this.approved = approved; + if (responseTypes != null) { + this.responseTypes = new HashSet(responseTypes); + } + this.redirectUri = redirectUri; + if (extensionProperties != null) { + this.extensions = extensionProperties; + } + } + + protected OAuth2Request(OAuth2Request other) { + this(other.getRequestParameters(), other.getClientId(), other.getAuthorities(), other.isApproved(), other + .getScope(), other.getResourceIds(), other.getRedirectUri(), other.getResponseTypes(), other + .getExtensions()); + } + + protected OAuth2Request(String clientId) { + setClientId(clientId); + } + + protected OAuth2Request() { + super(); + } + + public String getRedirectUri() { + return redirectUri; + } + + public Set getResponseTypes() { + return responseTypes; + } + + public Collection getAuthorities() { + return authorities; + } + + public boolean isApproved() { + return approved; + } + + public Set getResourceIds() { + return resourceIds; + } + + public Map getExtensions() { + return extensions; + } + + /** + * Update the request parameters and return a new object with the same properties except the parameters. + * @param parameters new parameters replacing the existing ones + * @return a new OAuth2Request + */ + public OAuth2Request createOAuth2Request(Map parameters) { + return new OAuth2Request(parameters, getClientId(), authorities, approved, getScope(), resourceIds, + redirectUri, responseTypes, extensions); + } + + /** + * Update the scope and create a new request. All the other properties are the same (including the request + * parameters). + * + * @param scope the new scope + * @return a new request with the narrowed scope + */ + public OAuth2Request narrowScope(Set scope) { + OAuth2Request request = new OAuth2Request(getRequestParameters(), getClientId(), authorities, approved, scope, + resourceIds, redirectUri, responseTypes, extensions); + request.refresh = this.refresh; + return request; + } + + public OAuth2Request refresh(TokenRequest tokenRequest) { + OAuth2Request request = new OAuth2Request(getRequestParameters(), getClientId(), authorities, approved, + getScope(), resourceIds, redirectUri, responseTypes, extensions); + request.refresh = tokenRequest; + return request; + } + + /** + * @return true if this request is known to be for a token to be refreshed + */ + public boolean isRefresh() { + return refresh != null; + } + + /** + * If this request was for an access token to be refreshed, then the {@link TokenRequest} that led to the refresh + * may be available here if it is known. + * + * @return the refresh token request (may be null) + */ + public TokenRequest getRefreshTokenRequest() { + return refresh; + } + + /** + * Tries to discover the grant type requested for the token associated with this request. + * + * @return the grant type if known, or null otherwise + */ + public String getGrantType() { + if (getRequestParameters().containsKey(OAuth2Utils.GRANT_TYPE)) { + return getRequestParameters().get(OAuth2Utils.GRANT_TYPE); + } + if (getRequestParameters().containsKey(OAuth2Utils.RESPONSE_TYPE)) { + String response = getRequestParameters().get(OAuth2Utils.RESPONSE_TYPE); + if (response.contains("token")) { + return "implicit"; + } + } + return null; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (approved ? 1231 : 1237); + result = prime * result + ((authorities == null) ? 0 : authorities.hashCode()); + result = prime * result + ((extensions == null) ? 0 : extensions.hashCode()); + result = prime * result + ((redirectUri == null) ? 0 : redirectUri.hashCode()); + result = prime * result + ((resourceIds == null) ? 0 : resourceIds.hashCode()); + result = prime * result + ((responseTypes == null) ? 0 : responseTypes.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + OAuth2Request other = (OAuth2Request) obj; + if (approved != other.approved) + return false; + if (authorities == null) { + if (other.authorities != null) + return false; + } + else if (!authorities.equals(other.authorities)) + return false; + if (extensions == null) { + if (other.extensions != null) + return false; + } + else if (!extensions.equals(other.extensions)) + return false; + if (redirectUri == null) { + if (other.redirectUri != null) + return false; + } + else if (!redirectUri.equals(other.redirectUri)) + return false; + if (resourceIds == null) { + if (other.resourceIds != null) + return false; + } + else if (!resourceIds.equals(other.resourceIds)) + return false; + if (responseTypes == null) { + if (other.responseTypes != null) + return false; + } + else if (!responseTypes.equals(other.responseTypes)) + return false; + return true; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/OAuth2RequestFactory.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/OAuth2RequestFactory.java new file mode 100644 index 000000000..22762e649 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/OAuth2RequestFactory.java @@ -0,0 +1,85 @@ +/* + * Copyright 2006-2011 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.provider; + +import java.util.Map; + +/** + * Strategy for managing OAuth2 requests: {@link AuthorizationRequest}, {@link TokenRequest}, {@link OAuth2Request}. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * @author Amanda Anganes + * + */ +@Deprecated +public interface OAuth2RequestFactory { + + /** + * Create a new {@link AuthorizationRequest} extracting all the needed information from the incoming parameter map, + * and initializing all individual fields on the {@link AuthorizationRequest} to reasonable values. When a class + * uses the factory to create an {@link AuthorizationRequest}, it should not need to access the parameter map + * directly afterwards. + * + * Typical implementations would initialize the individual fields on the {@link AuthorizationRequest} with the + * values requested in the original parameter map. It may also load the client details from the client id provided + * and validate the grant type and scopes, populating any fields in the request that are known only to the + * authorization server. + * + * @param authorizationParameters the parameters in the request + * @return a new AuthorizationRequest + */ + AuthorizationRequest createAuthorizationRequest(Map authorizationParameters); + + /** + * Create a new {@link OAuth2Request} by extracting the needed information from the current + * {@link AuthorizationRequest} object. + * + * @param request the request to be converted + * @return an immutable object for storage + */ + OAuth2Request createOAuth2Request(AuthorizationRequest request); + + /** + * Create a new {@link OAuth2Request} by extracting the needed information from the current {@link TokenRequest} + * object. + * @param client TODO + * @param tokenRequest the request to be converted + * + * @return am immutable object for storage + */ + OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest); + + /** + * Create a new {@link TokenRequest} by extracting the needed information from the incoming request parameter map. + * + * @param requestParameters the parameters in the request + * @param authenticatedClient the client that authenticated during the token request + * @return a new TokenRequest + */ + TokenRequest createTokenRequest(Map requestParameters, ClientDetails authenticatedClient); + + /** + * Create a new {@link TokenRequest} from an {@link AuthorizationRequest}. Principally used by the + * AuthorizationEndpoint during the implicit flow. + * + * @param authorizationRequest the incoming request + * @param grantType the grant type for the token request + * @return a new token request + */ + TokenRequest createTokenRequest(AuthorizationRequest authorizationRequest, String grantType); + +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/OAuth2RequestValidator.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/OAuth2RequestValidator.java new file mode 100644 index 000000000..548ab4a6d --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/OAuth2RequestValidator.java @@ -0,0 +1,37 @@ +package org.springframework.security.oauth2.provider; + +import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; +import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint; +import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint; + +/** + * Validation interface for OAuth2 requests to the {@link AuthorizationEndpoint} and {@link TokenEndpoint}. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Amanda Anganes + * + */ +@Deprecated +public interface OAuth2RequestValidator { + + /** + * Ensure that the client has requested a valid set of scopes. + * + * @param authorizationRequest the AuthorizationRequest to be validated + * @param client the client that is making the request + * @throws InvalidScopeException if a requested scope is invalid + */ + public void validateScope(AuthorizationRequest authorizationRequest, ClientDetails client) throws InvalidScopeException; + + /** + * Ensure that the client has requested a valid set of scopes. + * + * @param tokenRequest the TokenRequest to be validated + * @param client the client that is making the request + * @throws InvalidScopeException if a requested scope is invalid + */ + public void validateScope(TokenRequest tokenRequest, ClientDetails client) throws InvalidScopeException; + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/SaltedClientSecret.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/SaltedClientSecret.java deleted file mode 100644 index 7ac3dc4b7..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/SaltedClientSecret.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.springframework.security.oauth2.provider; - -/** - * Marker interface for indicating that a client details secret has some salt. - */ -public interface SaltedClientSecret { - - /** - * Returns the salt to use for this client secret. - * - * @return the salt to use for this client secret. - */ - Object getSalt(); -} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/SecurityContextAccessor.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/SecurityContextAccessor.java new file mode 100644 index 000000000..c051179f4 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/SecurityContextAccessor.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013-2014 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.provider; + +import java.util.Set; + +import org.springframework.security.core.GrantedAuthority; + +/** + * Strategy for accessing useful information about the current security context. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public interface SecurityContextAccessor { + + /** + * @return true if the current context represents a user + */ + boolean isUser(); + + /** + * Get the current granted authorities (never null) + */ + Set getAuthorities(); + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/TokenGranter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/TokenGranter.java index 1e5b52513..7c31f571c 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/TokenGranter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/TokenGranter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -21,12 +21,16 @@ /** * Interface for granters of access tokens. Various grant types are defined in the specification, and each of those has * an implementation, leaving room for extensions to the specification as needed. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface TokenGranter { - OAuth2AccessToken grant(String grantType, AuthorizationRequest authorizationRequest); + OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/TokenRequest.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/TokenRequest.java new file mode 100644 index 000000000..b04919afe --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/TokenRequest.java @@ -0,0 +1,102 @@ +package org.springframework.security.oauth2.provider; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint; +import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint; + +/** + * Represents an OAuth2 token request, made at the {@link TokenEndpoint}. The requestParameters map should contain the + * original, unmodified parameters from the original OAuth2 request. + * + * In the implicit flow, a token is requested through the {@link AuthorizationEndpoint} directly, and in that case the + * {@link AuthorizationRequest} is converted into a {@link TokenRequest} for processing through the token granting + * chain. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Amanda Anganes + * @author Dave Syer + * + */ +@SuppressWarnings("serial") +@Deprecated +public class TokenRequest extends BaseRequest { + + private String grantType; + + /** + * Default constructor + */ + protected TokenRequest() { + } + + /** + * Full constructor. Sets this TokenRequest's requestParameters map to an unmodifiable version of the one provided. + * + * @param requestParameters + * @param clientId + * @param scope + * @param grantType + */ + public TokenRequest(Map requestParameters, String clientId, Collection scope, + String grantType) { + setClientId(clientId); + setRequestParameters(requestParameters); + setScope(scope); + this.grantType = grantType; + } + + public String getGrantType() { + return grantType; + } + + public void setGrantType(String grantType) { + this.grantType = grantType; + } + + public void setClientId(String clientId) { + super.setClientId(clientId); + } + + /** + * Set the scope value. If the collection contains only a single scope value, this method will parse that value into + * a collection using {@link OAuth2Utils#parseParameterList}. + * + * @see AuthorizationRequest#setScope + * + * @param scope + */ + public void setScope(Collection scope) { + super.setScope(scope); + } + + /** + * Set the Request Parameters on this authorization request, which represent the original request parameters and + * should never be changed during processing. The map passed in is wrapped in an unmodifiable map instance. + * + * @see AuthorizationRequest#setRequestParameters + * + * @param requestParameters + */ + public void setRequestParameters(Map requestParameters) { + super.setRequestParameters(requestParameters); + } + + public OAuth2Request createOAuth2Request(ClientDetails client) { + Map requestParameters = getRequestParameters(); + HashMap modifiable = new HashMap(requestParameters); + // Remove password if present to prevent leaks + modifiable.remove("password"); + modifiable.remove("client_secret"); + // Add grant type so it can be retrieved from OAuth2Request + modifiable.put(OAuth2Utils.GRANT_TYPE, grantType); + return new OAuth2Request(modifiable, client.getClientId(), client.getAuthorities(), true, this.getScope(), + client.getResourceIds(), null, null, null); + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/Approval.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/Approval.java new file mode 100644 index 000000000..c9a2503f9 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/Approval.java @@ -0,0 +1,172 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.approval; + +import java.util.Calendar; +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import org.springframework.security.oauth2.common.util.JsonDateDeserializer; +import org.springframework.security.oauth2.common.util.JsonDateSerializer; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * @author Vidya Val + * + */ +@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) +@Deprecated +public class Approval { + + private String userId; + + private String clientId; + + private String scope; + + public enum ApprovalStatus { + APPROVED, + DENIED; + } + + private ApprovalStatus status; + + private Date expiresAt; + + private Date lastUpdatedAt; + + public Approval(String userId, String clientId, String scope, int expiresIn, ApprovalStatus status) { + this(userId, clientId, scope, new Date(), status, new Date()); + Calendar expiresAt = Calendar.getInstance(); + expiresAt.add(Calendar.MILLISECOND, expiresIn); + setExpiresAt(expiresAt.getTime()); + } + + public Approval(String userId, String clientId, String scope, Date expiresAt, ApprovalStatus status) { + this(userId, clientId, scope, expiresAt, status, new Date()); + } + + public Approval(String userId, String clientId, String scope, Date expiresAt, ApprovalStatus status, Date lastUpdatedAt) { + setUserId(userId); + setClientId(clientId); + setScope(scope); + setExpiresAt(expiresAt); + this.status = status; + this.lastUpdatedAt = lastUpdatedAt; + } + + protected Approval() { } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId == null ? "" : userId; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId == null ? "" : clientId; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope == null ? "" : scope; + } + + @JsonSerialize(using = JsonDateSerializer.class, include = JsonSerialize.Inclusion.NON_NULL) + public Date getExpiresAt() { + return expiresAt; + } + + @JsonDeserialize(using = JsonDateDeserializer.class) + public void setExpiresAt(Date expiresAt) { + if (expiresAt == null) { + Calendar thirtyMinFromNow = Calendar.getInstance(); + thirtyMinFromNow.add(Calendar.MINUTE, 30); + expiresAt = thirtyMinFromNow.getTime(); + } + this.expiresAt = expiresAt; + } + + @JsonSerialize(using = JsonDateSerializer.class, include = JsonSerialize.Inclusion.NON_NULL) + public Date getLastUpdatedAt() { + return lastUpdatedAt; + } + + @JsonDeserialize(using = JsonDateDeserializer.class) + public void setLastUpdatedAt(Date lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + @JsonIgnore + public boolean isCurrentlyActive() { + return expiresAt != null && expiresAt.after(new Date()); + } + + @JsonIgnore + public boolean isApproved() { + return isCurrentlyActive() && status==ApprovalStatus.APPROVED; + } + + public void setStatus(ApprovalStatus status) { + this.status = status; + } + + public ApprovalStatus getStatus() { + return status; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + userId.hashCode(); + result = prime * result + clientId.hashCode(); + result = prime * result + scope.hashCode(); + result = prime * result + status.hashCode(); + return result; + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof Approval)) { + return false; + } + Approval other = (Approval) o; + return userId.equals(other.userId) && clientId.equals(other.clientId) && scope.equals(other.scope) && status == other.status; + } + + @Override + public String toString() { + return String.format("[%s, %s, %s, %s, %s, %s]", userId, scope, clientId, expiresAt, status.toString(), lastUpdatedAt); + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/ApprovalStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/ApprovalStore.java new file mode 100644 index 000000000..85a67f4d3 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/ApprovalStore.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.approval; + +import java.util.Collection; + +/** + * Interface for saving, retrieving and revoking user approvals (per client, per scope). + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public interface ApprovalStore { + + public boolean addApprovals(Collection approvals); + + public boolean revokeApprovals(Collection approvals); + + public Collection getApprovals(String userId, String clientId); + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/ApprovalStoreUserApprovalHandler.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/ApprovalStoreUserApprovalHandler.java new file mode 100644 index 000000000..2bda1ae40 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/ApprovalStoreUserApprovalHandler.java @@ -0,0 +1,260 @@ +/* + * Copyright 2002-2011 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.provider.approval; + +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.ClientRegistrationException; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.approval.Approval.ApprovalStatus; +import org.springframework.util.Assert; + +/** + * A user approval handler that remembers approval decisions by consulting existing approvals. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class ApprovalStoreUserApprovalHandler implements UserApprovalHandler, InitializingBean { + + private static Log logger = LogFactory.getLog(ApprovalStoreUserApprovalHandler.class); + + private String scopePrefix = OAuth2Utils.SCOPE_PREFIX; + + private ApprovalStore approvalStore; + + private int approvalExpirySeconds = -1; + + private ClientDetailsService clientDetailsService; + + /** + * Service to load client details (optional) for auto approval checks. + * + * @param clientDetailsService a client details service + */ + public void setClientDetailsService(ClientDetailsService clientDetailsService) { + this.clientDetailsService = clientDetailsService; + } + + /** + * The prefix applied to incoming parameters that signal approval or denial of a scope. + * + * @param scopePrefix the prefix (default {@link OAuth2Utils#SCOPE_PREFIX}) + */ + public void setScopePrefix(String scopePrefix) { + this.scopePrefix = scopePrefix; + } + + /** + * @param store the approval to set + */ + public void setApprovalStore(ApprovalStore store) { + this.approvalStore = store; + } + + private OAuth2RequestFactory requestFactory; + + public void setRequestFactory(OAuth2RequestFactory requestFactory) { + this.requestFactory = requestFactory; + } + + public void setApprovalExpiryInSeconds(int approvalExpirySeconds) { + this.approvalExpirySeconds = approvalExpirySeconds; + } + + public void afterPropertiesSet() { + Assert.state(approvalStore != null, "ApprovalStore must be provided"); + Assert.state(requestFactory != null, "OAuth2RequestFactory must be provided"); + } + + public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + return authorizationRequest.isApproved(); + } + + public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + + String clientId = authorizationRequest.getClientId(); + Collection requestedScopes = authorizationRequest.getScope(); + Set approvedScopes = new HashSet(); + Set validUserApprovedScopes = new HashSet(); + + if (clientDetailsService != null) { + try { + ClientDetails client = clientDetailsService.loadClientByClientId(clientId); + for (String scope : requestedScopes) { + if (client.isAutoApprove(scope)) { + approvedScopes.add(scope); + } + } + if (approvedScopes.containsAll(requestedScopes)) { + // gh-877 - if all scopes are auto approved, approvals still need to be added to the approval store. + Set approvals = new HashSet(); + Date expiry = computeExpiry(); + for (String approvedScope : approvedScopes) { + approvals.add(new Approval(userAuthentication.getName(), authorizationRequest.getClientId(), + approvedScope, expiry, ApprovalStatus.APPROVED)); + } + approvalStore.addApprovals(approvals); + + authorizationRequest.setApproved(true); + return authorizationRequest; + } + } + catch (ClientRegistrationException e) { + logger.warn("Client registration problem prevent autoapproval check for client"); + } + } + + if (logger.isDebugEnabled()) { + StringBuilder builder = new StringBuilder("Looking up user approved authorizations for "); + builder.append("client_id=" + clientId); + builder.append(" and username=" + userAuthentication.getName()); + logger.debug(builder.toString()); + } + + // Find the stored approvals for that user and client + Collection userApprovals = approvalStore.getApprovals(userAuthentication.getName(), clientId); + + // Look at the scopes and see if they have expired + Date today = new Date(); + for (Approval approval : userApprovals) { + if (approval.getExpiresAt().after(today)) { + if (approval.getStatus() == ApprovalStatus.APPROVED) { + validUserApprovedScopes.add(approval.getScope()); + approvedScopes.add(approval.getScope()); + } + } + } + + if (logger.isDebugEnabled()) { + logger.debug("Valid user approved/denied scopes are " + validUserApprovedScopes); + } + + // If the requested scopes have already been acted upon by the user, + // this request is approved + if (validUserApprovedScopes.containsAll(requestedScopes)) { + approvedScopes.retainAll(requestedScopes); + // Set only the scopes that have been approved by the user + authorizationRequest.setScope(approvedScopes); + authorizationRequest.setApproved(true); + } + + return authorizationRequest; + + } + + private Date computeExpiry() { + Calendar expiresAt = Calendar.getInstance(); + if (approvalExpirySeconds == -1) { // use default of 1 month + expiresAt.add(Calendar.MONTH, 1); + } + else { + expiresAt.add(Calendar.SECOND, approvalExpirySeconds); + } + return expiresAt.getTime(); + } + + /** + * Requires the authorization request to be explicitly approved, including all individual scopes, and the user to be + * authenticated. A scope that was requested in the authorization request can be approved by sending a request + * parameter scope.<scopename> equal to "true" or "approved" (otherwise it will be assumed to + * have been denied). The {@link ApprovalStore} will be updated to reflect the inputs. + * + * @param authorizationRequest The authorization request. + * @param userAuthentication the current user authentication + * + * @return An approved request if all scopes have been approved by the current user. + */ + public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + // Get the approved scopes + Set requestedScopes = authorizationRequest.getScope(); + Set approvedScopes = new HashSet(); + Set approvals = new HashSet(); + + Date expiry = computeExpiry(); + + // Store the scopes that have been approved / denied + Map approvalParameters = authorizationRequest.getApprovalParameters(); + for (String requestedScope : requestedScopes) { + String approvalParameter = scopePrefix + requestedScope; + String value = approvalParameters.get(approvalParameter); + value = value == null ? "" : value.toLowerCase(); + if ("true".equals(value) || value.startsWith("approve")) { + approvedScopes.add(requestedScope); + approvals.add(new Approval(userAuthentication.getName(), authorizationRequest.getClientId(), + requestedScope, expiry, ApprovalStatus.APPROVED)); + } + else { + approvals.add(new Approval(userAuthentication.getName(), authorizationRequest.getClientId(), + requestedScope, expiry, ApprovalStatus.DENIED)); + } + } + approvalStore.addApprovals(approvals); + + boolean approved; + authorizationRequest.setScope(approvedScopes); + if (approvedScopes.isEmpty() && !requestedScopes.isEmpty()) { + approved = false; + } + else { + approved = true; + } + authorizationRequest.setApproved(approved); + return authorizationRequest; + } + + @Override + public Map getUserApprovalRequest(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + Map model = new HashMap(); + model.putAll(authorizationRequest.getRequestParameters()); + Map scopes = new LinkedHashMap(); + for (String scope : authorizationRequest.getScope()) { + scopes.put(scopePrefix + scope, "false"); + } + for (Approval approval : approvalStore.getApprovals(userAuthentication.getName(), + authorizationRequest.getClientId())) { + if (authorizationRequest.getScope().contains(approval.getScope())) { + scopes.put(scopePrefix + approval.getScope(), + approval.getStatus() == ApprovalStatus.APPROVED ? "true" : "false"); + } + } + model.put("scopes", scopes); + return model; + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/DefaultUserApprovalHandler.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/DefaultUserApprovalHandler.java index 72c8f4fb9..5aa3813b1 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/DefaultUserApprovalHandler.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/DefaultUserApprovalHandler.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,18 +16,26 @@ package org.springframework.security.oauth2.provider.approval; +import java.util.HashMap; +import java.util.Map; + import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.AuthorizationRequest; /** * A default user approval handler that doesn't remember any decisions. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class DefaultUserApprovalHandler implements UserApprovalHandler { - private String approvalParameter = AuthorizationRequest.USER_OAUTH_APPROVAL; + private String approvalParameter = OAuth2Utils.USER_OAUTH_APPROVAL; /** * @param approvalParameter the approvalParameter to set @@ -35,10 +43,6 @@ public class DefaultUserApprovalHandler implements UserApprovalHandler { public void setApprovalParameter(String approvalParameter) { this.approvalParameter = approvalParameter; } - - public AuthorizationRequest updateBeforeApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { - return authorizationRequest; - } /** * Basic implementation just requires the authorization request to be explicitly approved and the user to be @@ -50,9 +54,32 @@ public AuthorizationRequest updateBeforeApproval(AuthorizationRequest authorizat * @return Whether the specified request has been approved by the current user. */ public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { - String flag = authorizationRequest.getApprovalParameters().get(approvalParameter); + if (authorizationRequest.isApproved()) { + return true; + } + return false; + } + + public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + return authorizationRequest; + } + + @Override + public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + Map approvalParameters = authorizationRequest.getApprovalParameters(); + String flag = approvalParameters.get(approvalParameter); boolean approved = flag != null && flag.toLowerCase().equals("true"); - return userAuthentication.isAuthenticated() && approved; + authorizationRequest.setApproved(approved); + return authorizationRequest; + } + + @Override + public Map getUserApprovalRequest(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + Map model = new HashMap(); + // In case of a redirect we might want the request parameters to be included + model.putAll(authorizationRequest.getRequestParameters()); + return model; } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/InMemoryApprovalStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/InMemoryApprovalStore.java new file mode 100644 index 000000000..51b765905 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/InMemoryApprovalStore.java @@ -0,0 +1,125 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.approval; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class InMemoryApprovalStore implements ApprovalStore { + + private ConcurrentMap> map = new ConcurrentHashMap>(); + + @Override + public boolean addApprovals(Collection approvals) { + for (Approval approval : approvals) { + Collection collection = getApprovals(approval); + collection.add(approval); + } + return true; + } + + @Override + public boolean revokeApprovals(Collection approvals) { + boolean success = true; + for (Approval approval : approvals) { + Collection collection = getApprovals(approval); + boolean removed = collection.remove(approval); + if (!removed) { + success = false; + } + } + return success; + } + + private Collection getApprovals(Approval approval) { + Key key = new Key(approval.getUserId(), approval.getClientId()); + if (!map.containsKey(key)) { + map.putIfAbsent(key, new HashSet()); + } + return map.get(key); + } + + @Override + public Collection getApprovals(String userId, String clientId) { + Approval approval = new Approval(); + approval.setUserId(userId); + approval.setClientId(clientId); + return Collections.unmodifiableCollection(getApprovals(approval)); + } + + public void clear() { + map.clear(); + } + + private static class Key { + + String userId; + + String clientId; + + public Key(String userId, String clientId) { + this.userId = userId; + this.clientId = clientId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((clientId == null) ? 0 : clientId.hashCode()); + result = prime * result + ((userId == null) ? 0 : userId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Key other = (Key) obj; + if (clientId == null) { + if (other.clientId != null) + return false; + } + else if (!clientId.equals(other.clientId)) + return false; + if (userId == null) { + if (other.userId != null) + return false; + } + else if (!userId.equals(other.userId)) + return false; + return true; + } + + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/JdbcApprovalStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/JdbcApprovalStore.java new file mode 100644 index 000000000..11e5bfb7f --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/JdbcApprovalStore.java @@ -0,0 +1,233 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.approval; + +import static org.springframework.security.oauth2.provider.approval.Approval.ApprovalStatus.APPROVED; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementSetter; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.security.oauth2.provider.approval.Approval.ApprovalStatus; +import org.springframework.util.Assert; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class JdbcApprovalStore implements ApprovalStore { + + private final JdbcTemplate jdbcTemplate; + + private final Log logger = LogFactory.getLog(getClass()); + + private final RowMapper rowMapper = new AuthorizationRowMapper(); + + private static final String TABLE_NAME = "oauth_approvals"; + + private static final String FIELDS = "expiresAt,status,lastModifiedAt,userId,clientId,scope"; + + private static final String WHERE_KEY = "where userId=? and clientId=?"; + + private static final String WHERE_KEY_AND_SCOPE = WHERE_KEY + " and scope=?"; + + private static final String DEFAULT_ADD_APPROVAL_STATEMENT = String.format("insert into %s ( %s ) values (?,?,?,?,?,?)", TABLE_NAME, + FIELDS); + + private static final String DEFAULT_REFRESH_APPROVAL_STATEMENT = String.format( + "update %s set expiresAt=?, status=?, lastModifiedAt=? " + WHERE_KEY_AND_SCOPE, TABLE_NAME); + + private static final String DEFAULT_GET_APPROVAL_SQL = String.format("select %s from %s " + WHERE_KEY, FIELDS, TABLE_NAME); + + private static final String DEFAULT_DELETE_APPROVAL_SQL = String.format("delete from %s " + WHERE_KEY_AND_SCOPE, + TABLE_NAME); + + private static final String DEFAULT_EXPIRE_APPROVAL_STATEMENT = String.format("update %s set expiresAt = ? " + WHERE_KEY_AND_SCOPE, + TABLE_NAME); + + private String addApprovalStatement = DEFAULT_ADD_APPROVAL_STATEMENT; + + private String refreshApprovalStatement = DEFAULT_REFRESH_APPROVAL_STATEMENT; + + private String findApprovalStatement = DEFAULT_GET_APPROVAL_SQL; + + private String deleteApprovalStatment = DEFAULT_DELETE_APPROVAL_SQL; + + private String expireApprovalStatement = DEFAULT_EXPIRE_APPROVAL_STATEMENT; + + private boolean handleRevocationsAsExpiry = false; + + public JdbcApprovalStore(DataSource dataSource) { + Assert.notNull(dataSource); + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + public void setHandleRevocationsAsExpiry(boolean handleRevocationsAsExpiry) { + this.handleRevocationsAsExpiry = handleRevocationsAsExpiry; + } + + public void setAddApprovalStatement(String addApprovalStatement) { + this.addApprovalStatement = addApprovalStatement; + } + + public void setFindApprovalStatement(String findApprovalStatement) { + this.findApprovalStatement = findApprovalStatement; + } + + public void setDeleteApprovalStatment(String deleteApprovalStatment) { + this.deleteApprovalStatment = deleteApprovalStatment; + } + + public void setExpireApprovalStatement(String expireApprovalStatement) { + this.expireApprovalStatement = expireApprovalStatement; + } + + public void setRefreshApprovalStatement(String refreshApprovalStatement) { + this.refreshApprovalStatement = refreshApprovalStatement; + } + + @Override + public boolean addApprovals(final Collection approvals) { + if (logger.isDebugEnabled()) { + logger.debug(String.format("adding approvals: [%s]", approvals)); + } + boolean success = true; + for (Approval approval : approvals) { + if (!updateApproval(refreshApprovalStatement, approval)) { + if (!updateApproval(addApprovalStatement, approval)) { + success = false; + } + } + } + return success; + } + + @Override + public boolean revokeApprovals(Collection approvals) { + if (logger.isDebugEnabled()) { + logger.debug(String.format("Revoking approvals: [%s]", approvals)); + } + boolean success = true; + for (final Approval approval : approvals) { + if (handleRevocationsAsExpiry) { + int refreshed = jdbcTemplate.update(expireApprovalStatement, new PreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps) throws SQLException { + ps.setTimestamp(1, new Timestamp(System.currentTimeMillis())); + ps.setString(2, approval.getUserId()); + ps.setString(3, approval.getClientId()); + ps.setString(4, approval.getScope()); + } + }); + if (refreshed != 1) { + success = false; + } + } + else { + int refreshed = jdbcTemplate.update(deleteApprovalStatment, new PreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps) throws SQLException { + ps.setString(1, approval.getUserId()); + ps.setString(2, approval.getClientId()); + ps.setString(3, approval.getScope()); + } + }); + if (refreshed != 1) { + success = false; + } + } + } + return success; + } + + public boolean purgeExpiredApprovals() { + logger.debug("Purging expired approvals from database"); + try { + int deleted = jdbcTemplate.update(deleteApprovalStatment + " where expiresAt <= ?", + new PreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps) throws SQLException { + ps.setTimestamp(1, new Timestamp(new Date().getTime())); + } + }); + if (logger.isDebugEnabled()) { + logger.debug(deleted + " expired approvals deleted"); + } + } + catch (DataAccessException ex) { + logger.error("Error purging expired approvals", ex); + return false; + } + return true; + } + + @Override + public List getApprovals(String userName, String clientId) { + return jdbcTemplate.query(findApprovalStatement, rowMapper, userName, clientId); + } + + private boolean updateApproval(final String sql, final Approval approval) { + if (logger.isDebugEnabled()) { + logger.debug(String.format("refreshing approval: [%s]", approval)); + } + int refreshed = jdbcTemplate.update(sql, new PreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps) throws SQLException { + ps.setTimestamp(1, new Timestamp(approval.getExpiresAt().getTime())); + ps.setString(2, (approval.getStatus() == null ? APPROVED : approval.getStatus()).toString()); + ps.setTimestamp(3, new Timestamp(approval.getLastUpdatedAt().getTime())); + ps.setString(4, approval.getUserId()); + ps.setString(5, approval.getClientId()); + ps.setString(6, approval.getScope()); + } + }); + if (refreshed != 1) { + return false; + } + return true; + } + + private static class AuthorizationRowMapper implements RowMapper { + + @Override + public Approval mapRow(ResultSet rs, int rowNum) throws SQLException { + String userName = rs.getString(4); + String clientId = rs.getString(5); + String scope = rs.getString(6); + Date expiresAt = rs.getTimestamp(1); + String status = rs.getString(2); + Date lastUpdatedAt = rs.getTimestamp(3); + + return new Approval(userName, clientId, scope, expiresAt, ApprovalStatus.valueOf(status), lastUpdatedAt); + } + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/TokenApprovalStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/TokenApprovalStore.java new file mode 100644 index 000000000..2a45946f6 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/TokenApprovalStore.java @@ -0,0 +1,108 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.approval; + +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; + +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.approval.Approval.ApprovalStatus; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; +import org.springframework.security.oauth2.provider.token.TokenStore; + +/** + * An {@link ApprovalStore} that works with an existing {@link TokenStore}, extracting implicit {@link Approval + * Approvals} from the content of tokens already in the store. Useful interface so that users can list and revoke + * approvals even if they are not really represented in such a way internally. For full fine-grained control of user + * approvals don't use a TokenStore at all, and don't use this ApprovalStore with Approval-based + * {@link AuthorizationServerTokenServices} implementations. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class TokenApprovalStore implements ApprovalStore { + + private TokenStore store; + + /** + * @param store the token store to set + */ + public void setTokenStore(TokenStore store) { + this.store = store; + } + + /** + * This implementation is a no-op. We assume that the {@link TokenStore} is populated elsewhere, by (for example) a + * token services instance that knows more about granted tokens than we could possibly infer from the approvals. + * + * @see org.springframework.security.oauth2.provider.approval.ApprovalStore#addApprovals(java.util.Collection) + */ + @Override + public boolean addApprovals(Collection approvals) { + return true; + } + + /** + * Revoke all tokens that match the client and user in the approvals supplied. + * + * @see org.springframework.security.oauth2.provider.approval.ApprovalStore#revokeApprovals(java.util.Collection) + */ + @Override + public boolean revokeApprovals(Collection approvals) { + boolean success = true; + for (Approval approval : approvals) { + Collection tokens = store.findTokensByClientIdAndUserName(approval.getClientId(), approval.getUserId()); + for (OAuth2AccessToken token : tokens) { + OAuth2Authentication authentication = store.readAuthentication(token); + if (authentication != null + && approval.getClientId().equals(authentication.getOAuth2Request().getClientId())) { + store.removeAccessToken(token); + } + } + } + return success; + } + + /** + * Extract the implied approvals from any tokens associated with the user and client id supplied. + * + * @see org.springframework.security.oauth2.provider.approval.ApprovalStore#getApprovals(java.lang.String, + * java.lang.String) + */ + @Override + public Collection getApprovals(String userId, String clientId) { + Collection result = new HashSet(); + Collection tokens = store.findTokensByClientIdAndUserName(clientId, userId); + for (OAuth2AccessToken token : tokens) { + OAuth2Authentication authentication = store.readAuthentication(token); + if (authentication != null) { + Date expiresAt = token.getExpiration(); + for (String scope : token.getScope()) { + result.add(new Approval(userId, clientId, scope, expiresAt, ApprovalStatus.APPROVED)); + } + } + } + return result; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/TokenServicesUserApprovalHandler.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/TokenServicesUserApprovalHandler.java deleted file mode 100644 index 7651cdd09..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/TokenServicesUserApprovalHandler.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2002-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.provider.approval; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.provider.AuthorizationRequest; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; -import org.springframework.util.Assert; - -/** - * A user approval handler that remembers approval decisions by consulting existing tokens. - * - * @author Dave Syer - * - */ -public class TokenServicesUserApprovalHandler implements UserApprovalHandler, InitializingBean { - - private static Log logger = LogFactory.getLog(TokenServicesUserApprovalHandler.class); - - private String approvalParameter = AuthorizationRequest.USER_OAUTH_APPROVAL; - - /** - * @param approvalParameter the approvalParameter to set - */ - public void setApprovalParameter(String approvalParameter) { - this.approvalParameter = approvalParameter; - } - - private AuthorizationServerTokenServices tokenServices; - - /** - * @param tokenServices the token services to set - */ - public void setTokenServices(AuthorizationServerTokenServices tokenServices) { - this.tokenServices = tokenServices; - } - - public void afterPropertiesSet() { - Assert.state(tokenServices != null, "AuthorizationServerTokenServices must be provided"); - } - - public AuthorizationRequest updateBeforeApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { - return authorizationRequest; - } - - /** - * Basic implementation just requires the authorization request to be explicitly approved and the user to be - * authenticated. - * - * @param authorizationRequest The authorization request. - * @param userAuthentication the current user authentication - * - * @return Whether the specified request has been approved by the current user. - */ - public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { - - String flag = authorizationRequest.getApprovalParameters().get(approvalParameter); - boolean approved = flag != null && flag.toLowerCase().equals("true"); - - OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest, userAuthentication); - if (logger.isDebugEnabled()) { - StringBuilder builder = new StringBuilder("Looking up existing token for "); - builder.append("client_id=" + authorizationRequest.getClientId()); - builder.append(", scope=" + authorizationRequest.getScope()); - builder.append(" and username=" + userAuthentication.getName()); - logger.debug(builder.toString()); - } - - OAuth2AccessToken accessToken = tokenServices.getAccessToken(authentication); - logger.debug("Existing access token=" + accessToken); - if (accessToken != null && !accessToken.isExpired()) { - logger.debug("User already approved with token=" + accessToken); - // A token was already granted and is still valid, so this is already approved - approved = true; - } - else { - logger.debug("Checking explicit approval"); - approved = userAuthentication.isAuthenticated() && approved; - } - - return approved; - - } -} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/TokenStoreUserApprovalHandler.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/TokenStoreUserApprovalHandler.java new file mode 100644 index 000000000..8c60bea7f --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/TokenStoreUserApprovalHandler.java @@ -0,0 +1,183 @@ +/* + * Copyright 2002-2011 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.provider.approval; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.ClientRegistrationException; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.util.Assert; + +/** + * A user approval handler that remembers approval decisions by consulting existing tokens. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class TokenStoreUserApprovalHandler implements UserApprovalHandler, InitializingBean { + + private static Log logger = LogFactory.getLog(TokenStoreUserApprovalHandler.class); + + private String approvalParameter = OAuth2Utils.USER_OAUTH_APPROVAL; + + private TokenStore tokenStore; + + private ClientDetailsService clientDetailsService; + + /** + * Service to load client details (optional) for auto approval checks. + * + * @param clientDetailsService a client details service + */ + public void setClientDetailsService(ClientDetailsService clientDetailsService) { + this.clientDetailsService = clientDetailsService; + } + + /** + * @param approvalParameter the approvalParameter to set + */ + public void setApprovalParameter(String approvalParameter) { + this.approvalParameter = approvalParameter; + } + + /** + * @param tokenStore the token store to set + */ + public void setTokenStore(TokenStore tokenStore) { + this.tokenStore = tokenStore; + } + + private OAuth2RequestFactory requestFactory; + + public void setRequestFactory(OAuth2RequestFactory requestFactory) { + this.requestFactory = requestFactory; + } + + @Override + public void afterPropertiesSet() { + Assert.state(tokenStore != null, "TokenStore must be provided"); + Assert.state(requestFactory != null, "OAuth2RequestFactory must be provided"); + } + + /** + * Basic implementation just requires the authorization request to be explicitly approved and the user to be + * authenticated. + * + * @param authorizationRequest The authorization request. + * @param userAuthentication the current user authentication + * + * @return Whether the specified request has been approved by the current user. + */ + @Override + public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + return authorizationRequest.isApproved(); + } + + @Override + public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + + boolean approved = false; + + String clientId = authorizationRequest.getClientId(); + Set scopes = authorizationRequest.getScope(); + if (clientDetailsService!=null) { + try { + ClientDetails client = clientDetailsService.loadClientByClientId(clientId); + approved = true; + for (String scope : scopes) { + if (!client.isAutoApprove(scope)) { + approved = false; + } + } + if (approved) { + authorizationRequest.setApproved(true); + return authorizationRequest; + } + } + catch (ClientRegistrationException e) { + logger.warn("Client registration problem prevent autoapproval check for client=" + clientId); + } + } + + OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(authorizationRequest); + + OAuth2Authentication authentication = new OAuth2Authentication(storedOAuth2Request, userAuthentication); + if (logger.isDebugEnabled()) { + StringBuilder builder = new StringBuilder("Looking up existing token for "); + builder.append("client_id=" + clientId); + builder.append(", scope=" + scopes); + builder.append(" and username=" + userAuthentication.getName()); + logger.debug(builder.toString()); + } + + OAuth2AccessToken accessToken = tokenStore.getAccessToken(authentication); + if (logger.isDebugEnabled()) { + logger.debug("Existing access token=" + accessToken); + } + if (accessToken != null && !accessToken.isExpired()) { + if (logger.isDebugEnabled()) { + logger.debug("User already approved with token=" + accessToken); + } + // A token was already granted and is still valid, so this is already approved + approved = true; + } + else { + logger.debug("Checking explicit approval"); + approved = userAuthentication.isAuthenticated() && approved; + } + + authorizationRequest.setApproved(approved); + + return authorizationRequest; + } + + @Override + public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + Map approvalParameters = authorizationRequest.getApprovalParameters(); + String flag = approvalParameters.get(approvalParameter); + boolean approved = flag != null && flag.toLowerCase().equals("true"); + authorizationRequest.setApproved(approved); + return authorizationRequest; + } + + @Override + public Map getUserApprovalRequest(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + Map model = new HashMap(); + // In case of a redirect we might want the request parameters to be included + model.putAll(authorizationRequest.getRequestParameters()); + return model; + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/UserApprovalHandler.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/UserApprovalHandler.java index c9cfe1f82..e57ebba3d 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/UserApprovalHandler.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/approval/UserApprovalHandler.java @@ -1,38 +1,81 @@ package org.springframework.security.oauth2.provider.approval; +import java.util.Map; + import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.AuthorizationRequest; /** - * Basic interface for determining whether a given client authentication request has been approved by the current user. - * + * Basic interface for determining whether a given client authentication request has been + * approved by the current user. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer + * @author Amanda Anganes */ +@Deprecated public interface UserApprovalHandler { /** *

- * Provides an opportunity to update the authorization request before it is checked for approval in cases where the - * incoming approval parameters contain richer information than just true/false (e.g. some scopes are approved, and - * others are rejected), implementations may need to be able to modify the {@link AuthorizationRequest} before a - * token is generated from it. + * Tests whether the specified authorization request has been approved by the current + * user (if there is one). *

* * @param authorizationRequest the authorization request. - * @param userAuthentication TODO - * @return a new instance or the same one if no changes are required + * @param userAuthentication the user authentication for the current user. + * @return true if the request has been approved, false otherwise */ - AuthorizationRequest updateBeforeApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication); + boolean isApproved(AuthorizationRequest authorizationRequest, + Authentication userAuthentication); /** *

- * Tests whether the specified authorization request has been approved by the current user (if there is one). + * Provides a hook for allowing requests to be pre-approved (skipping the User + * Approval Page). Some implementations may allow users to store approval decisions so + * that they only have to approve a site once. This method is called in the + * AuthorizationEndpoint before sending the user to the Approval page. If this method + * sets oAuth2Request.approved to true, the Approval page will be skipped. *

* * @param authorizationRequest the authorization request. - * @param userAuthentication the user authentication for the current user. - * @return a new instance or the same one if no changes are required + * @param userAuthentication the user authentication + * @return the AuthorizationRequest, modified if necessary */ - boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication); + AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication); + + /** + *

+ * Provides an opportunity to update the authorization request after the + * {@link AuthorizationRequest#setApprovalParameters(Map) approval parameters} are set + * but before it is checked for approval. Useful in cases where the incoming approval + * parameters contain richer information than just true/false (e.g. some scopes are + * approved, and others are rejected), implementations may need to be able to modify + * the {@link AuthorizationRequest} before a token is generated from it. + *

+ * + * @param authorizationRequest the authorization request. + * @param userAuthentication the user authentication + * @return the AuthorizationRequest, modified if necessary + */ + AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication); + + /** + * Generate a request for the authorization server to ask for the user's approval. + * Typically this will be rendered into a view (HTML etc.) to prompt for the approval, + * so it needs to contain information about the grant (scopes and client id for + * instance). + * + * @param authorizationRequest the authorization request + * @param userAuthentication the user authentication + * @return a model map for rendering to the user to ask for approval + */ + Map getUserApprovalRequest(AuthorizationRequest authorizationRequest, + Authentication userAuthentication); + } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/BearerTokenExtractor.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/BearerTokenExtractor.java new file mode 100644 index 000000000..a11c6fae9 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/BearerTokenExtractor.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013-2014 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.provider.authentication; + +import java.util.Enumeration; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; + +/** + * {@link TokenExtractor} that strips the authenticator from a bearer token request (with an Authorization header in the + * form "Bearer <TOKEN>", or as a request parameter if that fails). The access token is the principal in + * the authentication token that is extracted. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class BearerTokenExtractor implements TokenExtractor { + + private final static Log logger = LogFactory.getLog(BearerTokenExtractor.class); + + @Override + public Authentication extract(HttpServletRequest request) { + String tokenValue = extractToken(request); + if (tokenValue != null) { + PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(tokenValue, ""); + return authentication; + } + return null; + } + + protected String extractToken(HttpServletRequest request) { + // first check the header... + String token = extractHeaderToken(request); + + // bearer type allows a request parameter as well + if (token == null) { + logger.debug("Token not found in headers. Trying request parameters."); + token = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN); + if (token == null) { + logger.debug("Token not found in request parameters. Not an OAuth2 request."); + } + else { + request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, OAuth2AccessToken.BEARER_TYPE); + } + } + + return token; + } + + /** + * Extract the OAuth bearer token from a header. + * + * @param request The request. + * @return The token, or null if no OAuth authorization header was supplied. + */ + protected String extractHeaderToken(HttpServletRequest request) { + Enumeration headers = request.getHeaders("Authorization"); + while (headers.hasMoreElements()) { // typically there is only one (most servers enforce that) + String value = headers.nextElement(); + if ((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) { + String authHeaderValue = value.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim(); + // Add this here for the auth details later. Would be better to change the signature of this method. + request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, + value.substring(0, OAuth2AccessToken.BEARER_TYPE.length()).trim()); + int commaIndex = authHeaderValue.indexOf(','); + if (commaIndex > 0) { + authHeaderValue = authHeaderValue.substring(0, commaIndex); + } + return authHeaderValue; + } + } + + return null; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationDetails.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationDetails.java index 04f3f8226..820ef1556 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationDetails.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationDetails.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -21,23 +21,34 @@ /** * A holder of selected HTTP details related to an OAuth2 authentication request. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class OAuth2AuthenticationDetails implements Serializable { private static final long serialVersionUID = -4809832298438307309L; public static final String ACCESS_TOKEN_VALUE = OAuth2AuthenticationDetails.class.getSimpleName() + ".ACCESS_TOKEN_VALUE"; + public static final String ACCESS_TOKEN_TYPE = OAuth2AuthenticationDetails.class.getSimpleName() + ".ACCESS_TOKEN_TYPE"; + private final String remoteAddress; private final String sessionId; private final String tokenValue; + private final String tokenType; + private final String display; + + private Object decodedDetails; + /** * Records the access token value and remote address and will also set the session Id if a session already exists @@ -47,6 +58,7 @@ public class OAuth2AuthenticationDetails implements Serializable { */ public OAuth2AuthenticationDetails(HttpServletRequest request) { this.tokenValue = (String) request.getAttribute(ACCESS_TOKEN_VALUE); + this.tokenType = (String) request.getAttribute(ACCESS_TOKEN_TYPE); this.remoteAddress = request.getRemoteAddr(); HttpSession session = request.getSession(false); @@ -55,16 +67,22 @@ public OAuth2AuthenticationDetails(HttpServletRequest request) { if (remoteAddress!=null) { builder.append("remoteAddress=").append(remoteAddress); } - if (builder.length()>1) { - builder.append(", "); - } if (sessionId!=null) { + if (builder.length() > 1) { + builder.append(", "); + } builder.append("sessionId="); } - if (builder.length()>1) { - builder.append(", "); + if (tokenType!=null) { + if (builder.length() > 1) { + builder.append(", "); + } + builder.append("tokenType=").append(this.tokenType); } if (tokenValue!=null) { + if (builder.length() > 1) { + builder.append(", "); + } builder.append("tokenValue="); } this.display = builder.toString(); @@ -78,6 +96,15 @@ public OAuth2AuthenticationDetails(HttpServletRequest request) { public String getTokenValue() { return tokenValue; } + + /** + * The access token type used to authenticate the request (normally in an authorization header). + * + * @return the tokenType used to authenticate the request if known + */ + public String getTokenType() { + return tokenType; + } /** * Indicates the TCP/IP address the authentication request was received from. @@ -97,9 +124,71 @@ public String getSessionId() { return sessionId; } + /** + * The authentication details obtained by decoding the access token + * if available. + * + * @return the decodedDetails if available (default null) + */ + public Object getDecodedDetails() { + return decodedDetails; + } + + /** + * The authentication details obtained by decoding the access token + * if available. + * + * @param decodedDetails the decodedDetails to set + */ + public void setDecodedDetails(Object decodedDetails) { + this.decodedDetails = decodedDetails; + } + @Override public String toString() { return display; } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode()); + result = prime * result + ((tokenType == null) ? 0 : tokenType.hashCode()); + result = prime * result + ((tokenValue == null) ? 0 : tokenValue.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + OAuth2AuthenticationDetails other = (OAuth2AuthenticationDetails) obj; + if (sessionId == null) { + if (other.sessionId != null) + return false; + } + else if (!sessionId.equals(other.sessionId)) + return false; + if (tokenType == null) { + if (other.tokenType != null) + return false; + } + else if (!tokenType.equals(other.tokenType)) + return false; + if (tokenValue == null) { + if (other.tokenValue != null) + return false; + } + else if (!tokenValue.equals(other.tokenValue)) + return false; + return true; + } + + + } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationDetailsSource.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationDetailsSource.java index 4dad52034..8358c77f3 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationDetailsSource.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationDetailsSource.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -20,10 +20,14 @@ /** * A source for authentication details in an OAuth2 protected Resource. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class OAuth2AuthenticationDetailsSource implements AuthenticationDetailsSource { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationManager.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationManager.java index da53a2a35..4d94f0598 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationManager.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationManager.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -13,6 +13,7 @@ package org.springframework.security.oauth2.provider.authentication; import java.util.Collection; +import java.util.Set; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.authentication.AuthenticationManager; @@ -21,26 +22,39 @@ import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.ClientRegistrationException; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; import org.springframework.util.Assert; /** * An {@link AuthenticationManager} for OAuth2 protected resources. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class OAuth2AuthenticationManager implements AuthenticationManager, InitializingBean { private ResourceServerTokenServices tokenServices; + private ClientDetailsService clientDetailsService; + private String resourceId; public void setResourceId(String resourceId) { this.resourceId = resourceId; } + public void setClientDetailsService(ClientDetailsService clientDetailsService) { + this.clientDetailsService = clientDetailsService; + } + /** * @param tokenServices the tokenServices to set */ @@ -54,7 +68,7 @@ public void afterPropertiesSet() { /** * Expects the incoming authentication request to have a principal value that is an access token value (e.g. from an - * authorization header) .Loads an authentication from the {@link ResourceServerTokenServices} and checks that the + * authorization header). Loads an authentication from the {@link ResourceServerTokenServices} and checks that the * resource id is contained in the {@link AuthorizationRequest} (if one is specified). Also copies authentication * details over from the input to the output (e.g. typically so that the access token value and request details can * be reported later). @@ -66,20 +80,53 @@ public void afterPropertiesSet() { */ public Authentication authenticate(Authentication authentication) throws AuthenticationException { + if (authentication == null) { + throw new InvalidTokenException("Invalid token (token not found)"); + } String token = (String) authentication.getPrincipal(); OAuth2Authentication auth = tokenServices.loadAuthentication(token); if (auth == null) { - throw new InvalidTokenException("Invalid token: " + token); + throw new InvalidTokenException("Invalid token"); } - Collection resourceIds = auth.getAuthorizationRequest().getResourceIds(); + Collection resourceIds = auth.getOAuth2Request().getResourceIds(); if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) { throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")"); } + checkClientDetails(auth); + + if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) { + OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails(); + // Guard against a cached copy of the same details + if (!details.equals(auth.getDetails())) { + // Preserve the authentication details from the one loaded by token services + details.setDecodedDetails(auth.getDetails()); + } + } auth.setDetails(authentication.getDetails()); + auth.setAuthenticated(true); return auth; } + private void checkClientDetails(OAuth2Authentication auth) { + if (clientDetailsService != null) { + ClientDetails client; + try { + client = clientDetailsService.loadClientByClientId(auth.getOAuth2Request().getClientId()); + } + catch (ClientRegistrationException e) { + throw new OAuth2AccessDeniedException("Invalid token contains invalid client id"); + } + Set allowed = client.getScope(); + for (String scope : auth.getOAuth2Request().getScope()) { + if (!allowed.contains(scope)) { + throw new OAuth2AccessDeniedException( + "Invalid token contains disallowed scope for this client"); + } + } + } + } + } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationProcessingFilter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationProcessingFilter.java index fe9b21294..7152691cb 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationProcessingFilter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationProcessingFilter.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -13,7 +13,6 @@ package org.springframework.security.oauth2.provider.authentication; import java.io.IOException; -import java.util.Enumeration; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -27,12 +26,16 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationDetailsSource; +import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; @@ -41,13 +44,17 @@ import org.springframework.util.Assert; /** - * A pre-authemtication filter for OAuth2 protected resources. Extracts an OAuth2 token from the in coming request and + * A pre-authentication filter for OAuth2 protected resources. Extracts an OAuth2 token from the incoming request and * uses it to populate the Spring Security context with an {@link OAuth2Authentication} (if used in conjunction with an * {@link OAuth2AuthenticationManager}). - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean { private final static Log logger = LogFactory.getLog(OAuth2AuthenticationProcessingFilter.class); @@ -58,6 +65,25 @@ public class OAuth2AuthenticationProcessingFilter implements Filter, Initializin private AuthenticationDetailsSource authenticationDetailsSource = new OAuth2AuthenticationDetailsSource(); + private TokenExtractor tokenExtractor = new BearerTokenExtractor(); + + private AuthenticationEventPublisher eventPublisher = new NullEventPublisher(); + + private boolean stateless = true; + + /** + * Flag to say that this filter guards stateless resources (default true). Set this to true if the only way the + * resource can be accessed is with a token. If false then an incoming cookie can populate the security context and + * allow access to a caller that isn't an OAuth2 client. When false, remember to also allow sessions to be created + * by configuring session management with a session creation policy that allows sessions to be set. + * See {@link org.springframework.security.config.http.SessionCreationPolicy} for your choices. + * + * @param stateless the flag to set (default true) + */ + public void setStateless(boolean stateless) { + this.stateless = stateless; + } + /** * @param authenticationEntryPoint the authentication entry point to set */ @@ -72,14 +98,28 @@ public void setAuthenticationManager(AuthenticationManager authenticationManager this.authenticationManager = authenticationManager; } - /** - * @param authenticationDetailsSource - * The AuthenticationDetailsSource to use - */ - public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) { - Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required"); - this.authenticationDetailsSource = authenticationDetailsSource; - } + /** + * @param tokenExtractor the tokenExtractor to set + */ + public void setTokenExtractor(TokenExtractor tokenExtractor) { + this.tokenExtractor = tokenExtractor; + } + + /** + * @param eventPublisher the event publisher to set + */ + public void setAuthenticationEventPublisher(AuthenticationEventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + } + + /** + * @param authenticationDetailsSource The AuthenticationDetailsSource to use + */ + public void setAuthenticationDetailsSource( + AuthenticationDetailsSource authenticationDetailsSource) { + Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required"); + this.authenticationDetailsSource = authenticationDetailsSource; + } public void afterPropertiesSet() { Assert.state(authenticationManager != null, "AuthenticationManager is required"); @@ -94,23 +134,32 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) try { - String tokenValue = parseToken(request); - if (tokenValue == null) { + Authentication authentication = tokenExtractor.extract(request); + + if (authentication == null) { + if (stateless && isAuthenticated()) { + if (debug) { + logger.debug("Clearing security context."); + } + SecurityContextHolder.clearContext(); + } if (debug) { logger.debug("No token in request, will continue chain."); } } else { - PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken( - tokenValue, ""); - request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, tokenValue); - authentication.setDetails(authenticationDetailsSource.buildDetails(request)); + request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal()); + if (authentication instanceof AbstractAuthenticationToken) { + AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication; + needsDetails.setDetails(authenticationDetailsSource.buildDetails(request)); + } Authentication authResult = authenticationManager.authenticate(authentication); if (debug) { logger.debug("Authentication success: " + authResult); } + eventPublisher.publishAuthenticationSuccess(authResult); SecurityContextHolder.getContext().setAuthentication(authResult); } @@ -121,6 +170,8 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) if (debug) { logger.debug("Authentication request failed: " + failed); } + eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed), + new PreAuthenticatedAuthenticationToken("access-token", "N/A")); authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(failed.getMessage(), failed)); @@ -131,48 +182,12 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) chain.doFilter(request, response); } - protected String parseToken(HttpServletRequest request) { - // first check the header... - String token = parseHeaderToken(request); - - // bearer type allows a request parameter as well - if (token == null) { - logger.debug("Token not found in headers. Trying request parameters."); - token = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN); - if (token == null) { - logger.debug("Token not found in request parameters. Not an OAuth2 request."); - } - } - - return token; - } - - /** - * Parse the OAuth header parameters. The parameters will be oauth-decoded. - * - * @param request The request. - * @return The parsed parameters, or null if no OAuth authorization header was supplied. - */ - protected String parseHeaderToken(HttpServletRequest request) { - @SuppressWarnings("unchecked") - Enumeration headers = request.getHeaders("Authorization"); - while (headers.hasMoreElements()) { // typically there is only one (most servers enforce that) - String value = headers.nextElement(); - if ((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) { - String authHeaderValue = value.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim(); - int commaIndex = authHeaderValue.indexOf(','); - if (commaIndex > 0) { - authHeaderValue = authHeaderValue.substring(0, commaIndex); - } - return authHeaderValue; - } - else { - // todo: support additional authorization schemes for different token types, e.g. "MAC" specified by - // http://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token - } + private boolean isAuthenticated() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || authentication instanceof AnonymousAuthenticationToken) { + return false; } - - return null; + return true; } public void init(FilterConfig filterConfig) throws ServletException { @@ -181,4 +196,12 @@ public void init(FilterConfig filterConfig) throws ServletException { public void destroy() { } + private static final class NullEventPublisher implements AuthenticationEventPublisher { + public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) { + } + + public void publishAuthenticationSuccess(Authentication authentication) { + } + } + } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/TokenExtractor.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/TokenExtractor.java new file mode 100644 index 000000000..2537b1eb2 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/TokenExtractor.java @@ -0,0 +1,38 @@ +/* + * Copyright 2013-2014 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.provider.authentication; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.security.core.Authentication; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public interface TokenExtractor { + + /** + * Extract a token value from an incoming request without authentication. + * + * @param request the current ServletRequest + * @return an authentication token whose principal is an access token (or null if there is none) + */ + Authentication extract(HttpServletRequest request); + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/BaseClientDetails.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/BaseClientDetails.java new file mode 100644 index 000000000..e1a82439e --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/BaseClientDetails.java @@ -0,0 +1,388 @@ +package org.springframework.security.oauth2.provider.client; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.util.StringUtils; + +/** + * Base implementation of + * {@link org.springframework.security.oauth2.provider.ClientDetails}. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Ryan Heaton + * @author Dave Syer + */ +@SuppressWarnings("serial") +@com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_DEFAULT) +@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true) +@Deprecated +public class BaseClientDetails implements ClientDetails { + + @com.fasterxml.jackson.annotation.JsonProperty("client_id") + private String clientId; + + @com.fasterxml.jackson.annotation.JsonProperty("client_secret") + private String clientSecret; + + @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = Jackson2ArrayOrStringDeserializer.class) + private Set scope = Collections.emptySet(); + + @com.fasterxml.jackson.annotation.JsonProperty("resource_ids") + @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = Jackson2ArrayOrStringDeserializer.class) + private Set resourceIds = Collections.emptySet(); + + @com.fasterxml.jackson.annotation.JsonProperty("authorized_grant_types") + @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = Jackson2ArrayOrStringDeserializer.class) + private Set authorizedGrantTypes = Collections.emptySet(); + + @com.fasterxml.jackson.annotation.JsonProperty("redirect_uri") + @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = Jackson2ArrayOrStringDeserializer.class) + private Set registeredRedirectUris; + + @com.fasterxml.jackson.annotation.JsonProperty("autoapprove") + @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = Jackson2ArrayOrStringDeserializer.class) + private Set autoApproveScopes; + + private List authorities = Collections.emptyList(); + + @com.fasterxml.jackson.annotation.JsonProperty("access_token_validity") + private Integer accessTokenValiditySeconds; + + @com.fasterxml.jackson.annotation.JsonProperty("refresh_token_validity") + private Integer refreshTokenValiditySeconds; + + @com.fasterxml.jackson.annotation.JsonIgnore + private Map additionalInformation = new LinkedHashMap(); + + public BaseClientDetails() { + } + + public BaseClientDetails(ClientDetails prototype) { + this(); + setAccessTokenValiditySeconds(prototype.getAccessTokenValiditySeconds()); + setRefreshTokenValiditySeconds(prototype + .getRefreshTokenValiditySeconds()); + setAuthorities(prototype.getAuthorities()); + setAuthorizedGrantTypes(prototype.getAuthorizedGrantTypes()); + setClientId(prototype.getClientId()); + setClientSecret(prototype.getClientSecret()); + setRegisteredRedirectUri(prototype.getRegisteredRedirectUri()); + setScope(prototype.getScope()); + setResourceIds(prototype.getResourceIds()); + } + + public BaseClientDetails(String clientId, String resourceIds, + String scopes, String grantTypes, String authorities) { + this(clientId, resourceIds, scopes, grantTypes, authorities, null); + } + + public BaseClientDetails(String clientId, String resourceIds, + String scopes, String grantTypes, String authorities, + String redirectUris) { + + this.clientId = clientId; + + if (StringUtils.hasText(resourceIds)) { + Set resources = StringUtils + .commaDelimitedListToSet(resourceIds); + if (!resources.isEmpty()) { + this.resourceIds = resources; + } + } + + if (StringUtils.hasText(scopes)) { + Set scopeList = StringUtils.commaDelimitedListToSet(scopes); + if (!scopeList.isEmpty()) { + this.scope = scopeList; + } + } + + if (StringUtils.hasText(grantTypes)) { + this.authorizedGrantTypes = StringUtils + .commaDelimitedListToSet(grantTypes); + } else { + this.authorizedGrantTypes = new HashSet(Arrays.asList( + "authorization_code", "refresh_token")); + } + + if (StringUtils.hasText(authorities)) { + this.authorities = AuthorityUtils + .commaSeparatedStringToAuthorityList(authorities); + } + + if (StringUtils.hasText(redirectUris)) { + this.registeredRedirectUris = StringUtils + .commaDelimitedListToSet(redirectUris); + } + } + + @com.fasterxml.jackson.annotation.JsonIgnore + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public void setAutoApproveScopes(Collection autoApproveScopes) { + this.autoApproveScopes = new HashSet(autoApproveScopes); + } + + @Override + public boolean isAutoApprove(String scope) { + if (autoApproveScopes == null) { + return false; + } + for (String auto : autoApproveScopes) { + if (auto.equals("true") || scope.matches(auto)) { + return true; + } + } + return false; + } + + @com.fasterxml.jackson.annotation.JsonIgnore + public Set getAutoApproveScopes() { + return autoApproveScopes; + } + + @com.fasterxml.jackson.annotation.JsonIgnore + public boolean isSecretRequired() { + return this.clientSecret != null; + } + + @com.fasterxml.jackson.annotation.JsonIgnore + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + @com.fasterxml.jackson.annotation.JsonIgnore + public boolean isScoped() { + return this.scope != null && !this.scope.isEmpty(); + } + + public Set getScope() { + return scope; + } + + public void setScope(Collection scope) { + this.scope = scope == null ? Collections. emptySet() + : new LinkedHashSet(scope); + } + + @com.fasterxml.jackson.annotation.JsonIgnore + public Set getResourceIds() { + return resourceIds; + } + + public void setResourceIds(Collection resourceIds) { + this.resourceIds = resourceIds == null ? Collections + . emptySet() : new LinkedHashSet(resourceIds); + } + + @com.fasterxml.jackson.annotation.JsonIgnore + public Set getAuthorizedGrantTypes() { + return authorizedGrantTypes; + } + + public void setAuthorizedGrantTypes(Collection authorizedGrantTypes) { + this.authorizedGrantTypes = new LinkedHashSet( + authorizedGrantTypes); + } + + @com.fasterxml.jackson.annotation.JsonIgnore + public Set getRegisteredRedirectUri() { + return registeredRedirectUris; + } + + public void setRegisteredRedirectUri(Set registeredRedirectUris) { + this.registeredRedirectUris = registeredRedirectUris == null ? null + : new LinkedHashSet(registeredRedirectUris); + } + + @com.fasterxml.jackson.annotation.JsonProperty("authorities") + private List getAuthoritiesAsStrings() { + return new ArrayList( + AuthorityUtils.authorityListToSet(authorities)); + } + + @com.fasterxml.jackson.annotation.JsonProperty("authorities") + @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = Jackson2ArrayOrStringDeserializer.class) + private void setAuthoritiesAsStrings(Set values) { + setAuthorities(AuthorityUtils.createAuthorityList(values + .toArray(new String[values.size()]))); + } + + @com.fasterxml.jackson.annotation.JsonIgnore + public Collection getAuthorities() { + return authorities; + } + + @com.fasterxml.jackson.annotation.JsonIgnore + public void setAuthorities( + Collection authorities) { + this.authorities = new ArrayList(authorities); + } + + @com.fasterxml.jackson.annotation.JsonIgnore + public Integer getAccessTokenValiditySeconds() { + return accessTokenValiditySeconds; + } + + public void setAccessTokenValiditySeconds(Integer accessTokenValiditySeconds) { + this.accessTokenValiditySeconds = accessTokenValiditySeconds; + } + + @com.fasterxml.jackson.annotation.JsonIgnore + public Integer getRefreshTokenValiditySeconds() { + return refreshTokenValiditySeconds; + } + + public void setRefreshTokenValiditySeconds( + Integer refreshTokenValiditySeconds) { + this.refreshTokenValiditySeconds = refreshTokenValiditySeconds; + } + + public void setAdditionalInformation(Map additionalInformation) { + this.additionalInformation = new LinkedHashMap( + additionalInformation); + } + + @com.fasterxml.jackson.annotation.JsonAnyGetter + public Map getAdditionalInformation() { + return Collections.unmodifiableMap(this.additionalInformation); + } + + @com.fasterxml.jackson.annotation.JsonAnySetter + public void addAdditionalInformation(String key, Object value) { + this.additionalInformation.put(key, value); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime + * result + + ((accessTokenValiditySeconds == null) ? 0 + : accessTokenValiditySeconds); + result = prime + * result + + ((refreshTokenValiditySeconds == null) ? 0 + : refreshTokenValiditySeconds); + result = prime * result + + ((authorities == null) ? 0 : authorities.hashCode()); + result = prime + * result + + ((authorizedGrantTypes == null) ? 0 : authorizedGrantTypes + .hashCode()); + result = prime * result + + ((clientId == null) ? 0 : clientId.hashCode()); + result = prime * result + + ((clientSecret == null) ? 0 : clientSecret.hashCode()); + result = prime + * result + + ((registeredRedirectUris == null) ? 0 + : registeredRedirectUris.hashCode()); + result = prime * result + + ((resourceIds == null) ? 0 : resourceIds.hashCode()); + result = prime * result + ((scope == null) ? 0 : scope.hashCode()); + result = prime * result + ((additionalInformation == null) ? 0 : additionalInformation.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BaseClientDetails other = (BaseClientDetails) obj; + if (accessTokenValiditySeconds == null) { + if (other.accessTokenValiditySeconds != null) + return false; + } else if (!accessTokenValiditySeconds.equals(other.accessTokenValiditySeconds)) + return false; + if (refreshTokenValiditySeconds == null) { + if (other.refreshTokenValiditySeconds != null) + return false; + } else if (!refreshTokenValiditySeconds.equals(other.refreshTokenValiditySeconds)) + return false; + if (authorities == null) { + if (other.authorities != null) + return false; + } else if (!authorities.equals(other.authorities)) + return false; + if (authorizedGrantTypes == null) { + if (other.authorizedGrantTypes != null) + return false; + } else if (!authorizedGrantTypes.equals(other.authorizedGrantTypes)) + return false; + if (clientId == null) { + if (other.clientId != null) + return false; + } else if (!clientId.equals(other.clientId)) + return false; + if (clientSecret == null) { + if (other.clientSecret != null) + return false; + } else if (!clientSecret.equals(other.clientSecret)) + return false; + if (registeredRedirectUris == null) { + if (other.registeredRedirectUris != null) + return false; + } else if (!registeredRedirectUris.equals(other.registeredRedirectUris)) + return false; + if (resourceIds == null) { + if (other.resourceIds != null) + return false; + } else if (!resourceIds.equals(other.resourceIds)) + return false; + if (scope == null) { + if (other.scope != null) + return false; + } else if (!scope.equals(other.scope)) + return false; + if (additionalInformation == null) { + if (other.additionalInformation != null) + return false; + } else if (!additionalInformation.equals(other.additionalInformation)) + return false; + return true; + } + + @Override + public String toString() { + return "BaseClientDetails [clientId=" + clientId + ", clientSecret=" + + clientSecret + ", scope=" + scope + ", resourceIds=" + + resourceIds + ", authorizedGrantTypes=" + + authorizedGrantTypes + ", registeredRedirectUris=" + + registeredRedirectUris + ", authorities=" + authorities + + ", accessTokenValiditySeconds=" + accessTokenValiditySeconds + + ", refreshTokenValiditySeconds=" + + refreshTokenValiditySeconds + ", additionalInformation=" + + additionalInformation + "]"; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/ClientCredentialsTokenEndpointFilter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/ClientCredentialsTokenEndpointFilter.java index 95f114876..f4fb58f33 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/ClientCredentialsTokenEndpointFilter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/ClientCredentialsTokenEndpointFilter.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -23,34 +23,50 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.common.exceptions.BadClientCredentialsException; import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.web.HttpRequestMethodNotSupportedException; /** * A filter and authentication endpoint for the OAuth2 Token Endpoint. Allows clients to authenticate using request * parameters if included as a security filter, as permitted by the specification (but not recommended). It is * recommended by the specification that you permit HTTP basic authentication for clients, and not use this filter at * all. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class ClientCredentialsTokenEndpointFilter extends AbstractAuthenticationProcessingFilter { - + private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint(); + private boolean allowOnlyPost = false; + public ClientCredentialsTokenEndpointFilter() { this("/oauth/token"); } - + public ClientCredentialsTokenEndpointFilter(String path) { super(path); + setRequiresAuthenticationRequestMatcher(new ClientCredentialsRequestMatcher(path)); + // If authentication fails the type is "Form" + ((OAuth2AuthenticationEntryPoint) authenticationEntryPoint).setTypeName("Form"); + } + + public void setAllowOnlyPost(boolean allowOnlyPost) { + this.allowOnlyPost = allowOnlyPost; } - + /** * @param authenticationEntryPoint the authentication entry point to set */ @@ -82,11 +98,22 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { + if (allowOnlyPost && !"POST".equalsIgnoreCase(request.getMethod())) { + throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[] { "POST" }); + } + String clientId = request.getParameter("client_id"); String clientSecret = request.getParameter("client_secret"); + // If the request is already authenticated we can assume that this + // filter is not needed + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.isAuthenticated()) { + return authentication; + } + if (clientId == null) { - return null; + throw new BadCredentialsException("No client credentials presented"); } if (clientSecret == null) { @@ -108,24 +135,39 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR chain.doFilter(request, response); } - @Override - protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) { - String uri = request.getRequestURI(); - int pathParamIndex = uri.indexOf(';'); + protected static class ClientCredentialsRequestMatcher implements RequestMatcher { + + private String path; + + public ClientCredentialsRequestMatcher(String path) { + this.path = path; - if (pathParamIndex > 0) { - // strip everything after the first semi-colon - uri = uri.substring(0, pathParamIndex); } - String clientId = request.getParameter("client_id"); + @Override + public boolean matches(HttpServletRequest request) { + String uri = request.getRequestURI(); + int pathParamIndex = uri.indexOf(';'); - if (clientId == null) { - // Give basic auth a chance to work instead (it's preferred anyway) - return false; + if (pathParamIndex > 0) { + // strip everything after the first semi-colon + uri = uri.substring(0, pathParamIndex); + } + + String clientId = request.getParameter("client_id"); + + if (clientId == null) { + // Give basic auth a chance to work instead (it's preferred anyway) + return false; + } + + if ("".equals(request.getContextPath())) { + return uri.endsWith(path); + } + + return uri.endsWith(request.getContextPath() + path); } - return super.requiresAuthentication(request, response); } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/ClientCredentialsTokenGranter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/ClientCredentialsTokenGranter.java index 9d3b46caf..e56d1593a 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/ClientCredentialsTokenGranter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/ClientCredentialsTokenGranter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -18,31 +18,48 @@ import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class ClientCredentialsTokenGranter extends AbstractTokenGranter { private static final String GRANT_TYPE = "client_credentials"; + private boolean allowRefresh = false; public ClientCredentialsTokenGranter(AuthorizationServerTokenServices tokenServices, - ClientDetailsService clientDetailsService) { - super(tokenServices, clientDetailsService, GRANT_TYPE); + ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { + this(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); + } + + protected ClientCredentialsTokenGranter(AuthorizationServerTokenServices tokenServices, + ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) { + super(tokenServices, clientDetailsService, requestFactory, grantType); + } + + public void setAllowRefresh(boolean allowRefresh) { + this.allowRefresh = allowRefresh; } @Override - public OAuth2AccessToken grant(String grantType, AuthorizationRequest authorizationRequest) { - OAuth2AccessToken token = super.grant(grantType, authorizationRequest); + public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { + OAuth2AccessToken token = super.grant(grantType, tokenRequest); if (token != null) { DefaultOAuth2AccessToken norefresh = new DefaultOAuth2AccessToken(token); - // The spec says that client credentials are not allowed to get a refresh token - norefresh.setRefreshToken(null); + // The spec says that client credentials should not be allowed to get a refresh token + if (!allowRefresh) { + norefresh.setRefreshToken(null); + } token = norefresh; } return token; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/ClientDetailsUserDetailsService.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/ClientDetailsUserDetailsService.java index e68757a51..bcc6c9967 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/ClientDetailsUserDetailsService.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/ClientDetailsUserDetailsService.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -19,11 +19,16 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.NoSuchClientException; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class ClientDetailsUserDetailsService implements UserDetailsService { private final ClientDetailsService clientDetailsService; @@ -41,7 +46,12 @@ public void setPasswordEncoder(PasswordEncoder passwordEncoder) { } public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username); + ClientDetails clientDetails; + try { + clientDetails = clientDetailsService.loadClientByClientId(username); + } catch (NoSuchClientException e) { + throw new UsernameNotFoundException(e.getMessage(), e); + } String clientSecret = clientDetails.getClientSecret(); if (clientSecret== null || clientSecret.trim().length()==0) { clientSecret = emptyPassword; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/InMemoryClientDetailsService.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/InMemoryClientDetailsService.java similarity index 57% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/InMemoryClientDetailsService.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/InMemoryClientDetailsService.java index ea1806f10..1ce56e397 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/InMemoryClientDetailsService.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/InMemoryClientDetailsService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,35 +14,39 @@ * limitations under the License. */ -package org.springframework.security.oauth2.provider; +package org.springframework.security.oauth2.provider.client; import java.util.HashMap; import java.util.Map; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.ClientRegistrationException; +import org.springframework.security.oauth2.provider.NoSuchClientException; /** * Basic, in-memory implementation of the client details service. * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@Deprecated public class InMemoryClientDetailsService implements ClientDetailsService { - private Map clientDetailsStore = new HashMap(); + private Map clientDetailsStore = new HashMap(); - public ClientDetails loadClientByClientId(String clientId) throws OAuth2Exception { + public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException { ClientDetails details = clientDetailsStore.get(clientId); if (details == null) { - throw new NoSuchClientException("No client with requested id: " + clientId); + throw new NoSuchClientException("No client with requested id"); } return details; } - public Map getClientDetailsStore() { - return clientDetailsStore; - } - public void setClientDetailsStore(Map clientDetailsStore) { - this.clientDetailsStore = clientDetailsStore; + this.clientDetailsStore = new HashMap(clientDetailsStore); } + } \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/Jackson2ArrayOrStringDeserializer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/Jackson2ArrayOrStringDeserializer.java new file mode 100644 index 000000000..4e6285c9b --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/Jackson2ArrayOrStringDeserializer.java @@ -0,0 +1,49 @@ +package org.springframework.security.oauth2.provider.client; + +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.util.StringUtils; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.type.SimpleType; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + */ +@SuppressWarnings("serial") +@Deprecated +public class Jackson2ArrayOrStringDeserializer extends StdDeserializer> { + + public Jackson2ArrayOrStringDeserializer() { + super(Set.class); + } + + @Override + public JavaType getValueType() { + return SimpleType.construct(String.class); + } + + @Override + public Set deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, + JsonProcessingException { + JsonToken token = jp.getCurrentToken(); + if (token.isScalarValue()) { + String list = jp.getText(); + list = list.replaceAll("\\s+", ","); + return new LinkedHashSet(Arrays.asList(StringUtils.commaDelimitedListToStringArray(list))); + } + return jp.readValueAs(new TypeReference>() { + }); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/JdbcClientDetailsService.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/JdbcClientDetailsService.java similarity index 72% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/JdbcClientDetailsService.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/JdbcClientDetailsService.java index 7420acde0..1c9439abb 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/JdbcClientDetailsService.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/client/JdbcClientDetailsService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,19 +14,21 @@ * limitations under the License. */ -package org.springframework.security.oauth2.provider; +package org.springframework.security.oauth2.provider.client; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; - +import java.util.Set; import javax.sql.DataSource; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.codehaus.jackson.map.ObjectMapper; + import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; @@ -37,21 +39,32 @@ import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.common.util.DefaultJdbcListFactory; import org.springframework.security.oauth2.common.util.JdbcListFactory; +import org.springframework.security.oauth2.provider.ClientAlreadyExistsException; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.ClientRegistrationService; +import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** * Basic, JDBC implementation of the client details service. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * */ +@Deprecated public class JdbcClientDetailsService implements ClientDetailsService, ClientRegistrationService { private static final Log logger = LogFactory.getLog(JdbcClientDetailsService.class); - private ObjectMapper mapper = new ObjectMapper(); + private JsonMapper mapper = createJsonMapper(); private static final String CLIENT_FIELDS_FOR_UPDATE = "resource_ids, scope, " + "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, " - + "refresh_token_validity, additional_information"; + + "refresh_token_validity, additional_information, autoapprove"; private static final String CLIENT_FIELDS = "client_secret, " + CLIENT_FIELDS_FOR_UPDATE; @@ -63,7 +76,7 @@ public class JdbcClientDetailsService implements ClientDetailsService, ClientReg private static final String DEFAULT_SELECT_STATEMENT = BASE_FIND_STATEMENT + " where client_id = ?"; private static final String DEFAULT_INSERT_STATEMENT = "insert into oauth_client_details (" + CLIENT_FIELDS - + ", client_id) values (?,?,?,?,?,?,?,?,?,?)"; + + ", client_id) values (?,?,?,?,?,?,?,?,?,?,?)"; private static final String DEFAULT_UPDATE_STATEMENT = "update oauth_client_details " + "set " + CLIENT_FIELDS_FOR_UPDATE.replaceAll(", ", "=?, ") + "=? where client_id = ?"; @@ -112,7 +125,7 @@ public ClientDetails loadClientByClientId(String clientId) throws InvalidClientE details = jdbcTemplate.queryForObject(selectClientDetailsSql, new ClientDetailsRowMapper(), clientId); } catch (EmptyResultDataAccessException e) { - throw new NoSuchClientException("No client with requested id: " + clientId); + throw new NoSuchClientException("No client with requested id"); } return details; @@ -123,28 +136,28 @@ public void addClientDetails(ClientDetails clientDetails) throws ClientAlreadyEx jdbcTemplate.update(insertClientDetailsSql, getFields(clientDetails)); } catch (DuplicateKeyException e) { - throw new ClientAlreadyExistsException("Client already exists: " + clientDetails.getClientId(), e); + throw new ClientAlreadyExistsException("Client already exists", e); } } public void updateClientDetails(ClientDetails clientDetails) throws NoSuchClientException { int count = jdbcTemplate.update(updateClientDetailsSql, getFieldsForUpdate(clientDetails)); if (count != 1) { - throw new NoSuchClientException("No client found with id = " + clientDetails.getClientId()); + throw new NoSuchClientException("No client found with requested id"); } } public void updateClientSecret(String clientId, String secret) throws NoSuchClientException { int count = jdbcTemplate.update(updateClientSecretSql, passwordEncoder.encode(secret), clientId); if (count != 1) { - throw new NoSuchClientException("No client found with id = " + clientId); + throw new NoSuchClientException("No client found with requested id"); } } public void removeClientDetails(String clientId) throws NoSuchClientException { int count = jdbcTemplate.update(deleteClientDetailsSql, clientId); if (count != 1) { - throw new NoSuchClientException("No client found with id = " + clientId); + throw new NoSuchClientException("No client found with requested id"); } } @@ -164,7 +177,7 @@ private Object[] getFields(ClientDetails clientDetails) { private Object[] getFieldsForUpdate(ClientDetails clientDetails) { String json = null; try { - json = mapper.writeValueAsString(clientDetails.getAdditionalInformation()); + json = mapper.write(clientDetails.getAdditionalInformation()); } catch (Exception e) { logger.warn("Could not serialize additional information: " + clientDetails, e); @@ -180,7 +193,21 @@ private Object[] getFieldsForUpdate(ClientDetails clientDetails) { .collectionToCommaDelimitedString(clientDetails.getRegisteredRedirectUri()) : null, clientDetails.getAuthorities() != null ? StringUtils.collectionToCommaDelimitedString(clientDetails .getAuthorities()) : null, clientDetails.getAccessTokenValiditySeconds(), - clientDetails.getRefreshTokenValiditySeconds(), json, clientDetails.getClientId() }; + clientDetails.getRefreshTokenValiditySeconds(), json, getAutoApproveScopes(clientDetails), + clientDetails.getClientId() }; + } + + private String getAutoApproveScopes(ClientDetails clientDetails) { + if (clientDetails.isAutoApprove("true")) { + return "true"; // all scopes autoapproved + } + Set scopes = new HashSet(); + for (String scope : clientDetails.getScope()) { + if (clientDetails.isAutoApprove(scope)) { + scopes.add(scope); + } + } + return StringUtils.collectionToCommaDelimitedString(scopes); } public void setSelectClientDetailsSql(String selectClientDetailsSql) { @@ -228,7 +255,7 @@ public void setRowMapper(RowMapper rowMapper) { * */ private static class ClientDetailsRowMapper implements RowMapper { - private ObjectMapper mapper = new ObjectMapper(); + private JsonMapper mapper = createJsonMapper(); public ClientDetails mapRow(ResultSet rs, int rowNum) throws SQLException { BaseClientDetails details = new BaseClientDetails(rs.getString(1), rs.getString(3), rs.getString(4), @@ -244,15 +271,60 @@ public ClientDetails mapRow(ResultSet rs, int rowNum) throws SQLException { if (json != null) { try { @SuppressWarnings("unchecked") - Map additionalInformation = mapper.readValue(json, Map.class); + Map additionalInformation = mapper.read(json, Map.class); details.setAdditionalInformation(additionalInformation); } catch (Exception e) { logger.warn("Could not decode JSON for additional information: " + details, e); } } + String scopes = rs.getString(11); + if (scopes != null) { + details.setAutoApproveScopes(StringUtils.commaDelimitedListToSet(scopes)); + } return details; } } + interface JsonMapper { + String write(Object input) throws Exception; + + T read(String input, Class type) throws Exception; + } + + private static JsonMapper createJsonMapper() { + if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", null)) { + return new Jackson2Mapper(); + } + return new NotSupportedJsonMapper(); + } + + private static class Jackson2Mapper implements JsonMapper { + private com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(); + + @Override + public String write(Object input) throws Exception { + return mapper.writeValueAsString(input); + } + + @Override + public T read(String input, Class type) throws Exception { + return mapper.readValue(input, type); + } + } + + private static class NotSupportedJsonMapper implements JsonMapper { + @Override + public String write(Object input) throws Exception { + throw new UnsupportedOperationException( + "Jackson 2 is not available so JSON conversion cannot be done"); + } + + @Override + public T read(String input, Class type) throws Exception { + throw new UnsupportedOperationException( + "Jackson 2 is not available so JSON conversion cannot be done"); + } + } + } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeServices.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeServices.java index 20cf112fa..809823649 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeServices.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeServices.java @@ -1,12 +1,17 @@ package org.springframework.security.oauth2.provider.code; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.provider.OAuth2Authentication; /** * Services for issuing and storing authorization codes. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@Deprecated public interface AuthorizationCodeServices { /** @@ -15,7 +20,7 @@ public interface AuthorizationCodeServices { * @param authentication The authentications to store. * @return The generated code. */ - String createAuthorizationCode(AuthorizationRequestHolder authentication); + String createAuthorizationCode(OAuth2Authentication authentication); /** * Consume a authorization code. @@ -24,7 +29,7 @@ public interface AuthorizationCodeServices { * @return The authentications associated with the code. * @throws InvalidGrantException If the authorization code is invalid or expired. */ - AuthorizationRequestHolder consumeAuthorizationCode(String code) + OAuth2Authentication consumeAuthorizationCode(String code) throws InvalidGrantException; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeTokenGranter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeTokenGranter.java index b3b4d17c0..968e677c8 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeTokenGranter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeTokenGranter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -22,21 +22,28 @@ import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException; -import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; /** * Token granter for the authorization code grant type. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class AuthorizationCodeTokenGranter extends AbstractTokenGranter { private static final String GRANT_TYPE = "authorization_code"; @@ -44,40 +51,45 @@ public class AuthorizationCodeTokenGranter extends AbstractTokenGranter { private final AuthorizationCodeServices authorizationCodeServices; public AuthorizationCodeTokenGranter(AuthorizationServerTokenServices tokenServices, - AuthorizationCodeServices authorizationCodeServices, ClientDetailsService clientDetailsService) { - super(tokenServices, clientDetailsService, GRANT_TYPE); + AuthorizationCodeServices authorizationCodeServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { + this(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory, GRANT_TYPE); + } + + protected AuthorizationCodeTokenGranter(AuthorizationServerTokenServices tokenServices, AuthorizationCodeServices authorizationCodeServices, + ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) { + super(tokenServices, clientDetailsService, requestFactory, grantType); this.authorizationCodeServices = authorizationCodeServices; } @Override - protected OAuth2Authentication getOAuth2Authentication(AuthorizationRequest authorizationRequest) { + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { - Map parameters = authorizationRequest.getAuthorizationParameters(); + Map parameters = tokenRequest.getRequestParameters(); String authorizationCode = parameters.get("code"); - String redirectUri = parameters.get(AuthorizationRequest.REDIRECT_URI); + String redirectUri = parameters.get(OAuth2Utils.REDIRECT_URI); if (authorizationCode == null) { - throw new OAuth2Exception("An authorization code must be supplied."); + throw new InvalidRequestException("An authorization code must be supplied."); } - AuthorizationRequestHolder storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode); + OAuth2Authentication storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode); if (storedAuth == null) { - throw new InvalidGrantException("Invalid authorization code: " + authorizationCode); + throw new InvalidGrantException("Invalid authorization code"); } - AuthorizationRequest pendingAuthorizationRequest = storedAuth.getAuthenticationRequest(); + OAuth2Request pendingOAuth2Request = storedAuth.getOAuth2Request(); // https://jira.springsource.org/browse/SECOAUTH-333 // This might be null, if the authorization was done without the redirect_uri parameter - String redirectUriApprovalParameter = pendingAuthorizationRequest.getAuthorizationParameters().get( - AuthorizationRequest.REDIRECT_URI); + String redirectUriApprovalParameter = pendingOAuth2Request.getRequestParameters().get( + OAuth2Utils.REDIRECT_URI); if ((redirectUri != null || redirectUriApprovalParameter != null) - && !pendingAuthorizationRequest.getRedirectUri().equals(redirectUri)) { + && !pendingOAuth2Request.getRedirectUri().equals(redirectUri)) { throw new RedirectMismatchException("Redirect URI mismatch."); } - String pendingClientId = pendingAuthorizationRequest.getClientId(); - String clientId = authorizationRequest.getClientId(); + String pendingClientId = pendingOAuth2Request.getClientId(); + String clientId = tokenRequest.getClientId(); if (clientId != null && !clientId.equals(pendingClientId)) { // just a sanity check. throw new InvalidClientException("Client ID mismatch"); @@ -87,17 +99,17 @@ protected OAuth2Authentication getOAuth2Authentication(AuthorizationRequest auth // in the pendingAuthorizationRequest. We do want to check that a secret is provided // in the token request, but that happens elsewhere. - Map combinedParameters = new HashMap(storedAuth.getAuthenticationRequest() - .getAuthorizationParameters()); + Map combinedParameters = new HashMap(pendingOAuth2Request + .getRequestParameters()); // Combine the parameters adding the new ones last so they override if there are any clashes combinedParameters.putAll(parameters); - // Similarly scopes are not required in the token request, so we don't make a comparison here, just - // enforce validity through the AuthorizationRequestFactory. - DefaultAuthorizationRequest outgoingRequest = new DefaultAuthorizationRequest(pendingAuthorizationRequest); - outgoingRequest.setAuthorizationParameters(combinedParameters); - + + // Make a new stored request with the combined parameters + OAuth2Request finalStoredOAuth2Request = pendingOAuth2Request.createOAuth2Request(combinedParameters); + Authentication userAuth = storedAuth.getUserAuthentication(); - return new OAuth2Authentication(outgoingRequest, userAuth); + + return new OAuth2Authentication(finalStoredOAuth2Request, userAuth); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/AuthorizationRequestHolder.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/AuthorizationRequestHolder.java deleted file mode 100644 index d8d94a80b..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/AuthorizationRequestHolder.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2002-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.provider.code; - -import java.io.Serializable; - -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.provider.AuthorizationRequest; - -/** - * Convenience class for {@link AuthorizationCodeServices} to store and retrieve. - * - * @author Dave Syer - * - */ -public class AuthorizationRequestHolder implements Serializable { - - private static final long serialVersionUID = 914967629530462926L; - - private final AuthorizationRequest authorizationRequest; - - private final Authentication userAuthentication; - - public AuthorizationRequestHolder( - AuthorizationRequest authorizationRequest, Authentication userAuthentication) { - this.authorizationRequest = authorizationRequest; - this.userAuthentication = userAuthentication; - } - - public AuthorizationRequest getAuthenticationRequest() { - return authorizationRequest; - } - - public Authentication getUserAuthentication() { - return userAuthentication; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((authorizationRequest == null) ? 0 : authorizationRequest.hashCode()); - result = prime * result + ((userAuthentication == null) ? 0 : userAuthentication.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - AuthorizationRequestHolder other = (AuthorizationRequestHolder) obj; - if (authorizationRequest == null) { - if (other.authorizationRequest != null) - return false; - } else if (!authorizationRequest.equals(other.authorizationRequest)) - return false; - if (userAuthentication == null) { - if (other.userAuthentication != null) - return false; - } else if (!userAuthentication.equals(other.userAuthentication)) - return false; - return true; - } - -} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/InMemoryAuthorizationCodeServices.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/InMemoryAuthorizationCodeServices.java index 98323827f..47e0d03be 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/InMemoryAuthorizationCodeServices.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/InMemoryAuthorizationCodeServices.java @@ -2,24 +2,30 @@ import java.util.concurrent.ConcurrentHashMap; +import org.springframework.security.oauth2.provider.OAuth2Authentication; + /** * Implementation of authorization code services that stores the codes and authentication in memory. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class InMemoryAuthorizationCodeServices extends RandomValueAuthorizationCodeServices { - protected final ConcurrentHashMap authorizationCodeStore = new ConcurrentHashMap(); + protected final ConcurrentHashMap authorizationCodeStore = new ConcurrentHashMap(); @Override - protected void store(String code, AuthorizationRequestHolder authentication) { + protected void store(String code, OAuth2Authentication authentication) { this.authorizationCodeStore.put(code, authentication); } @Override - public AuthorizationRequestHolder remove(String code) { - AuthorizationRequestHolder auth = this.authorizationCodeStore.remove(code); + public OAuth2Authentication remove(String code) { + OAuth2Authentication auth = this.authorizationCodeStore.remove(code); return auth; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/JdbcAuthorizationCodeServices.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/JdbcAuthorizationCodeServices.java index c1e01f7da..e7c90d42e 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/JdbcAuthorizationCodeServices.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/JdbcAuthorizationCodeServices.java @@ -11,14 +11,19 @@ import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.support.SqlLobValue; import org.springframework.security.oauth2.common.util.SerializationUtils; +import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.util.Assert; /** * Implementation of authorization code services that stores the codes and authentication in a database. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ken Dombeck * @author Dave Syer */ +@Deprecated public class JdbcAuthorizationCodeServices extends RandomValueAuthorizationCodeServices { private static final String DEFAULT_SELECT_STATEMENT = "select code, authentication from oauth_code where code = ?"; @@ -37,19 +42,19 @@ public JdbcAuthorizationCodeServices(DataSource dataSource) { } @Override - protected void store(String code, AuthorizationRequestHolder authentication) { + protected void store(String code, OAuth2Authentication authentication) { jdbcTemplate.update(insertAuthenticationSql, new Object[] { code, new SqlLobValue(SerializationUtils.serialize(authentication)) }, new int[] { Types.VARCHAR, Types.BLOB }); } - public AuthorizationRequestHolder remove(String code) { - AuthorizationRequestHolder authentication; + public OAuth2Authentication remove(String code) { + OAuth2Authentication authentication; try { authentication = jdbcTemplate.queryForObject(selectAuthenticationSql, - new RowMapper() { - public AuthorizationRequestHolder mapRow(ResultSet rs, int rowNum) + new RowMapper() { + public OAuth2Authentication mapRow(ResultSet rs, int rowNum) throws SQLException { return SerializationUtils.deserialize(rs.getBytes("authentication")); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/RandomValueAuthorizationCodeServices.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/RandomValueAuthorizationCodeServices.java index bd7dccd9f..154d12987 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/RandomValueAuthorizationCodeServices.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/RandomValueAuthorizationCodeServices.java @@ -2,32 +2,37 @@ import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import org.springframework.security.oauth2.provider.OAuth2Authentication; /** * Base implementation for authorization code services that generates a random-value authorization code. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public abstract class RandomValueAuthorizationCodeServices implements AuthorizationCodeServices { private RandomValueStringGenerator generator = new RandomValueStringGenerator(); - protected abstract void store(String code, AuthorizationRequestHolder authentication); + protected abstract void store(String code, OAuth2Authentication authentication); - protected abstract AuthorizationRequestHolder remove(String code); + protected abstract OAuth2Authentication remove(String code); - public String createAuthorizationCode(AuthorizationRequestHolder authentication) { + public String createAuthorizationCode(OAuth2Authentication authentication) { String code = generator.generate(); store(code, authentication); return code; } - public AuthorizationRequestHolder consumeAuthorizationCode(String code) + public OAuth2Authentication consumeAuthorizationCode(String code) throws InvalidGrantException { - AuthorizationRequestHolder auth = this.remove(code); + OAuth2Authentication auth = this.remove(code); if (auth == null) { - throw new InvalidGrantException("Invalid authorization code: " + code); + throw new InvalidGrantException("Invalid authorization code"); } return auth; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/RedisAuthorizationCodeServices.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/RedisAuthorizationCodeServices.java new file mode 100644 index 000000000..083bc72ca --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/RedisAuthorizationCodeServices.java @@ -0,0 +1,143 @@ +/* + * 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.provider.code; + +import java.lang.reflect.Method; +import java.util.List; + +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy; +import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * Implementation of authorization code services that stores the codes and authentication in Redis. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Stefan Rempfer + */ +@Deprecated +public class RedisAuthorizationCodeServices extends RandomValueAuthorizationCodeServices { + + private static final boolean springDataRedis_2_0 = ClassUtils.isPresent( + "org.springframework.data.redis.connection.RedisStandaloneConfiguration", + RedisAuthorizationCodeServices.class.getClassLoader()); + + private static final String AUTH_CODE = "auth_code:"; + + private final RedisConnectionFactory connectionFactory; + + private String prefix = ""; + + private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy(); + + private Method redisConnectionSet_2_0; + + /** + * Default constructor. + * + * @param connectionFactory the connection factory which should be used to obtain a connection to Redis + */ + public RedisAuthorizationCodeServices(RedisConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + if (springDataRedis_2_0) { + this.loadRedisConnectionMethods_2_0(); + } + } + + @Override + protected void store(String code, OAuth2Authentication authentication) { + byte[] key = serializeKey(AUTH_CODE + code); + byte[] auth = serialize(authentication); + + RedisConnection conn = getConnection(); + try { + if (springDataRedis_2_0) { + try { + this.redisConnectionSet_2_0.invoke(conn, key, auth); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } else { + conn.set(key, auth); + } + } + finally { + conn.close(); + } + } + + @Override + protected OAuth2Authentication remove(String code) { + byte[] key = serializeKey(AUTH_CODE + code); + + List results = null; + RedisConnection conn = getConnection(); + try { + conn.openPipeline(); + conn.get(key); + conn.del(key); + results = conn.closePipeline(); + } + finally { + conn.close(); + } + + if (results == null) { + return null; + } + byte[] bytes = (byte[]) results.get(0); + return deserializeAuthentication(bytes); + } + + private void loadRedisConnectionMethods_2_0() { + this.redisConnectionSet_2_0 = ReflectionUtils.findMethod( + RedisConnection.class, "set", byte[].class, byte[].class); + } + + private byte[] serializeKey(String object) { + return serialize(prefix + object); + } + + private byte[] serialize(Object object) { + return serializationStrategy.serialize(object); + } + + private byte[] serialize(String string) { + return serializationStrategy.serialize(string); + } + + private RedisConnection getConnection() { + return connectionFactory.getConnection(); + } + + private OAuth2Authentication deserializeAuthentication(byte[] bytes) { + return serializationStrategy.deserialize(bytes, OAuth2Authentication.class); + } + + public void setSerializationStrategy(RedisTokenStoreSerializationStrategy serializationStrategy) { + this.serializationStrategy = serializationStrategy; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/AbstractEndpoint.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/AbstractEndpoint.java index a36e18b42..db2b85a5c 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/AbstractEndpoint.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/AbstractEndpoint.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -19,42 +19,47 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.oauth2.provider.AuthorizationRequestManager; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequestManager; import org.springframework.security.oauth2.provider.TokenGranter; import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator; import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; import org.springframework.util.Assert; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class AbstractEndpoint implements InitializingBean { protected final Log logger = LogFactory.getLog(getClass()); - private WebResponseExceptionTranslator providerExceptionHandler = new DefaultWebResponseExceptionTranslator(); + private WebResponseExceptionTranslator providerExceptionHandler = new DefaultWebResponseExceptionTranslator(); private TokenGranter tokenGranter; private ClientDetailsService clientDetailsService; - private AuthorizationRequestManager authorizationRequestManager; + private OAuth2RequestFactory oAuth2RequestFactory; - private AuthorizationRequestManager defaultAuthorizationRequestManager; + private OAuth2RequestFactory defaultOAuth2RequestFactory; public void afterPropertiesSet() throws Exception { Assert.state(tokenGranter != null, "TokenGranter must be provided"); Assert.state(clientDetailsService != null, "ClientDetailsService must be provided"); - defaultAuthorizationRequestManager = new DefaultAuthorizationRequestManager(getClientDetailsService()); - if (authorizationRequestManager == null) { - authorizationRequestManager = defaultAuthorizationRequestManager; + defaultOAuth2RequestFactory = new DefaultOAuth2RequestFactory(getClientDetailsService()); + if (oAuth2RequestFactory == null) { + oAuth2RequestFactory = defaultOAuth2RequestFactory; } } - public void setProviderExceptionHandler(WebResponseExceptionTranslator providerExceptionHandler) { + public void setProviderExceptionHandler(WebResponseExceptionTranslator providerExceptionHandler) { this.providerExceptionHandler = providerExceptionHandler; } @@ -66,20 +71,20 @@ protected TokenGranter getTokenGranter() { return tokenGranter; } - protected WebResponseExceptionTranslator getExceptionTranslator() { + protected WebResponseExceptionTranslator getExceptionTranslator() { return providerExceptionHandler; } - protected AuthorizationRequestManager getAuthorizationRequestManager() { - return authorizationRequestManager; + protected OAuth2RequestFactory getOAuth2RequestFactory() { + return oAuth2RequestFactory; } - protected AuthorizationRequestManager getDefaultAuthorizationRequestManager() { - return defaultAuthorizationRequestManager; + protected OAuth2RequestFactory getDefaultOAuth2RequestFactory() { + return defaultOAuth2RequestFactory; } - public void setAuthorizationRequestManager(AuthorizationRequestManager authorizationRequestManager) { - this.authorizationRequestManager = authorizationRequestManager; + public void setOAuth2RequestFactory(OAuth2RequestFactory oAuth2RequestFactory) { + this.oAuth2RequestFactory = oAuth2RequestFactory; } protected ClientDetailsService getClientDetailsService() { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpoint.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpoint.java index 61b3599c1..2cc3bbcc1 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpoint.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpoint.java @@ -1,39 +1,29 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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.provider.endpoint; -import static org.springframework.security.oauth2.provider.AuthorizationRequest.REDIRECT_URI; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.security.Principal; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.springframework.beans.factory.InitializingBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.BadClientCredentialsException; import org.springframework.security.oauth2.common.exceptions.ClientAuthenticationException; -import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException; @@ -42,13 +32,19 @@ import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.AuthorizationRequest; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; -import org.springframework.security.oauth2.provider.NoSuchClientException; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientRegistrationException; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.OAuth2RequestValidator; +import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler; import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; -import org.springframework.security.oauth2.provider.code.AuthorizationRequestHolder; import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices; +import org.springframework.security.oauth2.provider.implicit.ImplicitTokenRequest; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.HttpSessionRequiredException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -59,10 +55,25 @@ import org.springframework.web.bind.support.DefaultSessionAttributeStore; import org.springframework.web.bind.support.SessionAttributeStore; import org.springframework.web.bind.support.SessionStatus; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.View; import org.springframework.web.servlet.view.RedirectView; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.servlet.http.HttpServletResponse; +import java.net.URI; +import java.security.Principal; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; /** *

@@ -76,15 +87,21 @@ * This endpoint should be secured so that it is only accessible to fully authenticated users (as a minimum requirement) * since it represents a request from a valid user to act on his or her behalf. *

- * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * @author Vladimir Kryachko * */ @FrameworkEndpoint -@SessionAttributes("authorizationRequest") -@RequestMapping(value = "/oauth/authorize") -public class AuthorizationEndpoint extends AbstractEndpoint implements InitializingBean { +@SessionAttributes({AuthorizationEndpoint.AUTHORIZATION_REQUEST_ATTR_NAME, AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME}) +@Deprecated +public class AuthorizationEndpoint extends AbstractEndpoint { + static final String AUTHORIZATION_REQUEST_ATTR_NAME = "authorizationRequest"; + + static final String ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME = "org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST"; private AuthorizationCodeServices authorizationCodeServices = new InMemoryAuthorizationCodeServices(); @@ -94,13 +111,13 @@ public class AuthorizationEndpoint extends AbstractEndpoint implements Initializ private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore(); + private OAuth2RequestValidator oauth2RequestValidator = new DefaultOAuth2RequestValidator(); + private String userApprovalPage = "forward:/oauth/confirm_access"; private String errorPage = "forward:/oauth/error"; - public void afterPropertiesSet() throws Exception { - super.afterPropertiesSet(); - } + private Object implicitLock = new Object(); public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) { this.sessionAttributeStore = sessionAttributeStore; @@ -110,55 +127,73 @@ public void setErrorPage(String errorPage) { this.errorPage = errorPage; } - @RequestMapping - public ModelAndView authorize(Map model, - @RequestParam(value = "response_type", required = false, defaultValue = "none") String responseType, - @RequestParam Map parameters, SessionStatus sessionStatus, Principal principal) { + @RequestMapping(value = "/oauth/authorize") + public ModelAndView authorize(Map model, @RequestParam Map parameters, + SessionStatus sessionStatus, Principal principal) { + + // Pull out the authorization request first, using the OAuth2RequestFactory. All further logic should + // query off of the authorization request instead of referring back to the parameters map. The contents of the + // parameters map will be stored without change in the AuthorizationRequest object once it is created. + AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters); - Set responseTypes = OAuth2Utils.parseParameterList(responseType); + Set responseTypes = authorizationRequest.getResponseTypes(); if (!responseTypes.contains("token") && !responseTypes.contains("code")) { - throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes); + throw new UnsupportedResponseTypeException("Unsupported response types"); } - try { + if (authorizationRequest.getClientId() == null) { + throw new InvalidClientException("A client id must be provided"); + } - // Manually initialize auth request instead of using @ModelAttribute - // to make sure it comes from request instead of the session - DefaultAuthorizationRequest incomingRequest = new DefaultAuthorizationRequest( - getAuthorizationRequestManager().createAuthorizationRequest(parameters)); + try { if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) { throw new InsufficientAuthenticationException( "User must be authenticated with Spring Security before authorization can be completed."); } - // The resolvedRedirectUri is either the redirect_uri from the parameters or the one from - // clientDetails. Either way we need to store it on the DefaultAuthorizationRequest - AuthorizationRequest outgoingRequest = resolveRedirectUriAndCheckApproval(incomingRequest, - (Authentication) principal); + ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId()); + + // The resolved redirect URI is either the redirect_uri from the parameters or the one from + // clientDetails. Either way we need to store it on the AuthorizationRequest. + String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI); + String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client); + if (!StringUtils.hasText(resolvedRedirect)) { + throw new RedirectMismatchException( + "A redirectUri must be either supplied or preconfigured in the ClientDetails"); + } + authorizationRequest.setRedirectUri(resolvedRedirect); // We intentionally only validate the parameters requested by the client (ignoring any data that may have // been added to the request by the manager). - getAuthorizationRequestManager().validateParameters(parameters, - getClientDetailsService().loadClientByClientId(outgoingRequest.getClientId())); + oauth2RequestValidator.validateScope(authorizationRequest, client); + + // Some systems may allow for approval decisions to be remembered or approved by default. Check for + // such logic here, and set the approved flag on the authorization request accordingly. + authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest, + (Authentication) principal); + // TODO: is this call necessary? + boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal); + authorizationRequest.setApproved(approved); // Validation is all done, so we can check for auto approval... - if (outgoingRequest.isApproved()) { + if (authorizationRequest.isApproved()) { if (responseTypes.contains("token")) { - return getImplicitGrantResponse(outgoingRequest); + return getImplicitGrantResponse(authorizationRequest); } if (responseTypes.contains("code")) { - return new ModelAndView(getAuthorizationCodeResponse(outgoingRequest, (Authentication) principal)); + return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest, + (Authentication) principal)); } } - // Place auth request into the model so that it is stored in the session - // for approveOrDeny to use. That way we make sure that auth request comes from the session, - // so any auth request parameters passed to approveOrDeny will be ignored and retrieved from the session. - model.put("authorizationRequest", outgoingRequest); + // Store authorizationRequest AND an immutable Map of authorizationRequest in session + // which will be used to validate against in approveOrDeny() + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, unmodifiableMap(authorizationRequest)); - return getUserApprovalPageResponse(model, outgoingRequest); + return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal); } catch (RuntimeException e) { @@ -168,7 +203,34 @@ public ModelAndView authorize(Map model, } - @RequestMapping(method = RequestMethod.POST, params = AuthorizationRequest.USER_OAUTH_APPROVAL) + Map unmodifiableMap(AuthorizationRequest authorizationRequest) { + Map authorizationRequestMap = new HashMap(); + + authorizationRequestMap.put(OAuth2Utils.CLIENT_ID, authorizationRequest.getClientId()); + authorizationRequestMap.put(OAuth2Utils.STATE, authorizationRequest.getState()); + authorizationRequestMap.put(OAuth2Utils.REDIRECT_URI, authorizationRequest.getRedirectUri()); + if (authorizationRequest.getResponseTypes() != null) { + authorizationRequestMap.put(OAuth2Utils.RESPONSE_TYPE, + Collections.unmodifiableSet(new HashSet(authorizationRequest.getResponseTypes()))); + } + if (authorizationRequest.getScope() != null) { + authorizationRequestMap.put(OAuth2Utils.SCOPE, + Collections.unmodifiableSet(new HashSet(authorizationRequest.getScope()))); + } + authorizationRequestMap.put("approved", authorizationRequest.isApproved()); + if (authorizationRequest.getResourceIds() != null) { + authorizationRequestMap.put("resourceIds", + Collections.unmodifiableSet(new HashSet(authorizationRequest.getResourceIds()))); + } + if (authorizationRequest.getAuthorities() != null) { + authorizationRequestMap.put("authorities", + Collections.unmodifiableSet(new HashSet(authorizationRequest.getAuthorities()))); + } + + return Collections.unmodifiableMap(authorizationRequestMap); + } + + @RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL) public View approveOrDeny(@RequestParam Map approvalParameters, Map model, SessionStatus sessionStatus, Principal principal) { @@ -178,32 +240,47 @@ public View approveOrDeny(@RequestParam Map approvalParameters, "User must be authenticated with Spring Security before authorizing an access token."); } - AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest"); + AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get(AUTHORIZATION_REQUEST_ATTR_NAME); if (authorizationRequest == null) { sessionStatus.setComplete(); throw new InvalidRequestException("Cannot approve uninitialized authorization request."); } + // Check to ensure the Authorization Request was not modified during the user approval step + @SuppressWarnings("unchecked") + Map originalAuthorizationRequest = (Map) model.get(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME); + if (isAuthorizationRequestModified(authorizationRequest, originalAuthorizationRequest)) { + throw new InvalidRequestException("Changes were detected from the original authorization request."); + } + try { Set responseTypes = authorizationRequest.getResponseTypes(); - DefaultAuthorizationRequest incomingRequest = new DefaultAuthorizationRequest(authorizationRequest); - incomingRequest.setApprovalParameters(approvalParameters); - - AuthorizationRequest outgoingRequest = resolveRedirectUriAndCheckApproval(incomingRequest, + authorizationRequest.setApprovalParameters(approvalParameters); + authorizationRequest = userApprovalHandler.updateAfterApproval(authorizationRequest, (Authentication) principal); + boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal); + authorizationRequest.setApproved(approved); - if (!outgoingRequest.isApproved()) { - return new RedirectView(getUnsuccessfulRedirect(outgoingRequest, new UserDeniedAuthorizationException( - "User denied access"), responseTypes.contains("token")), false); + if (authorizationRequest.getRedirectUri() == null) { + sessionStatus.setComplete(); + throw new InvalidRequestException("Cannot approve request when no redirect URI is provided."); + } + + if (!authorizationRequest.isApproved()) { + RedirectView redirectView = new RedirectView(getUnsuccessfulRedirect(authorizationRequest, + new UserDeniedAuthorizationException("User denied access"), responseTypes.contains("token")), + false, true, false); + redirectView.setStatusCode(HttpStatus.SEE_OTHER); + return redirectView; } if (responseTypes.contains("token")) { - return getImplicitGrantResponse(outgoingRequest).getView(); + return getImplicitGrantResponse(authorizationRequest).getView(); } - return getAuthorizationCodeResponse(outgoingRequest, (Authentication) principal); + return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal); } finally { sessionStatus.setComplete(); @@ -211,109 +288,146 @@ public View approveOrDeny(@RequestParam Map approvalParameters, } - /** - * Resolving and return the redirect uri if possible, throwing an exception otherwise, and then checking to see if - * the request has been authorized, setting the flag appropriately. - * - * @param authorizationRequest the current request - * @param authentication the current authentication token - * - * @return the resolved redirect uri ( - * {@link RedirectResolver#resolveRedirect(String, org.springframework.security.oauth2.provider.ClientDetails)} - * - * @throws OAuth2Exception if the redirect uri or client is invalid - */ - private AuthorizationRequest resolveRedirectUriAndCheckApproval(AuthorizationRequest authorizationRequest, - Authentication authentication) throws OAuth2Exception { - - String redirectUriParameter = authorizationRequest.getAuthorizationParameters().get( - AuthorizationRequest.REDIRECT_URI); - String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, getClientDetailsService() - .loadClientByClientId(authorizationRequest.getClientId())); - if (!StringUtils.hasText(resolvedRedirect)) { - throw new RedirectMismatchException( - "A redirectUri must be either supplied or preconfigured in the ClientDetails"); - } - - DefaultAuthorizationRequest requestForApproval = new DefaultAuthorizationRequest(authorizationRequest); - requestForApproval.setRedirectUri(resolvedRedirect); - DefaultAuthorizationRequest outgoingRequest = new DefaultAuthorizationRequest( - userApprovalHandler.updateBeforeApproval(requestForApproval, authentication)); - - boolean approved = authorizationRequest.isApproved(); - if (!approved) { - approved = userApprovalHandler.isApproved(outgoingRequest, authentication); - outgoingRequest.setApproved(approved); - } - - return outgoingRequest; + private boolean isAuthorizationRequestModified( + AuthorizationRequest authorizationRequest, Map originalAuthorizationRequest) { + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.getClientId(), + originalAuthorizationRequest.get(OAuth2Utils.CLIENT_ID))) { + return true; + } + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.getState(), + originalAuthorizationRequest.get(OAuth2Utils.STATE))) { + return true; + } + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.getRedirectUri(), + originalAuthorizationRequest.get(OAuth2Utils.REDIRECT_URI))) { + return true; + } + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.getResponseTypes(), + originalAuthorizationRequest.get(OAuth2Utils.RESPONSE_TYPE))) { + return true; + } + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.getScope(), + originalAuthorizationRequest.get(OAuth2Utils.SCOPE))) { + return true; + } + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.isApproved(), + originalAuthorizationRequest.get("approved"))) { + return true; + } + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.getResourceIds(), + originalAuthorizationRequest.get("resourceIds"))) { + return true; + } + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.getAuthorities(), + originalAuthorizationRequest.get("authorities"))) { + return true; + } + + return false; } // We need explicit approval from the user. private ModelAndView getUserApprovalPageResponse(Map model, - AuthorizationRequest authorizationRequest) { - logger.debug("Loading user approval page: " + userApprovalPage); - // In case of a redirect we might want the request parameters to be included - model.putAll(authorizationRequest.getAuthorizationParameters()); + AuthorizationRequest authorizationRequest, Authentication principal) { + if (logger.isDebugEnabled()) { + logger.debug("Loading user approval page: " + userApprovalPage); + } + model.putAll(userApprovalHandler.getUserApprovalRequest(authorizationRequest, principal)); return new ModelAndView(userApprovalPage, model); } // We can grant a token and return it with implicit approval. private ModelAndView getImplicitGrantResponse(AuthorizationRequest authorizationRequest) { try { - OAuth2AccessToken accessToken = getTokenGranter().grant("implicit", authorizationRequest); + TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(authorizationRequest, "implicit"); + OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest); + OAuth2AccessToken accessToken = getAccessTokenForImplicitGrant(tokenRequest, storedOAuth2Request); if (accessToken == null) { throw new UnsupportedResponseTypeException("Unsupported response type: token"); } - return new ModelAndView(new RedirectView(appendAccessToken(authorizationRequest, accessToken), false)); + setCacheControlHeaders(); + RedirectView redirectView = new RedirectView(appendAccessToken(authorizationRequest, accessToken), false, true, + false); + redirectView.setStatusCode(HttpStatus.SEE_OTHER); + return new ModelAndView(redirectView); } catch (OAuth2Exception e) { - return new ModelAndView(new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, true), false)); + RedirectView redirectView = new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, true), false, + true, false); + redirectView.setStatusCode(HttpStatus.SEE_OTHER); + return new ModelAndView(redirectView); + } + } + + private OAuth2AccessToken getAccessTokenForImplicitGrant(TokenRequest tokenRequest, + OAuth2Request storedOAuth2Request) { + OAuth2AccessToken accessToken = null; + // These 1 method calls have to be atomic, otherwise the ImplicitGrantService can have a race condition where + // one thread removes the token request before another has a chance to redeem it. + synchronized (this.implicitLock) { + accessToken = getTokenGranter().grant("implicit", + new ImplicitTokenRequest(tokenRequest, storedOAuth2Request)); } + return accessToken; } private View getAuthorizationCodeResponse(AuthorizationRequest authorizationRequest, Authentication authUser) { try { - return new RedirectView(getSuccessfulRedirect(authorizationRequest, - generateCode(authorizationRequest, authUser)), false); + RedirectView redirectView = new RedirectView(getSuccessfulRedirect(authorizationRequest, + generateCode(authorizationRequest, authUser)), false, true, false); + redirectView.setStatusCode(HttpStatus.SEE_OTHER); + return redirectView; } catch (OAuth2Exception e) { - return new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, false), false); + RedirectView redirectView = new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, false), false, true, false); + redirectView.setStatusCode(HttpStatus.SEE_OTHER); + return redirectView; } } private String appendAccessToken(AuthorizationRequest authorizationRequest, OAuth2AccessToken accessToken) { - String requestedRedirect = authorizationRequest.getRedirectUri(); + + Map vars = new LinkedHashMap(); + Map keys = new HashMap(); + if (accessToken == null) { - throw new InvalidGrantException("An implicit grant could not be made"); - } - StringBuilder url = new StringBuilder(requestedRedirect); - if (requestedRedirect.contains("#")) { - url.append("&"); - } - else { - url.append("#"); + throw new InvalidRequestException("An implicit grant could not be made"); } - url.append("access_token=" + accessToken.getValue()); - url.append("&token_type=" + accessToken.getTokenType()); + + vars.put("access_token", accessToken.getValue()); + vars.put("token_type", accessToken.getTokenType()); String state = authorizationRequest.getState(); + if (state != null) { - url.append("&state=" + state); + vars.put("state", state); } Date expiration = accessToken.getExpiration(); if (expiration != null) { long expires_in = (expiration.getTime() - System.currentTimeMillis()) / 1000; - url.append("&expires_in=" + expires_in); + vars.put("expires_in", expires_in); + } + String originalScope = authorizationRequest.getRequestParameters().get(OAuth2Utils.SCOPE); + if (originalScope == null || !OAuth2Utils.parseParameterList(originalScope).equals(accessToken.getScope())) { + vars.put(OAuth2Utils.SCOPE, OAuth2Utils.formatParameterList(accessToken.getScope())); } Map additionalInformation = accessToken.getAdditionalInformation(); for (String key : additionalInformation.keySet()) { Object value = additionalInformation.get(key); if (value != null) { - url.append("&" + key + "=" + value); // implicit call of .toString() here + keys.put("extra_" + key, key); + vars.put("extra_" + key, value); } } // Do not include the refresh token (even if there is one) - return url.toString(); + return append(authorizationRequest.getRedirectUri(), vars, keys, true); } private String generateCode(AuthorizationRequest authorizationRequest, Authentication authentication) @@ -321,8 +435,9 @@ private String generateCode(AuthorizationRequest authorizationRequest, Authentic try { - AuthorizationRequestHolder combinedAuth = new AuthorizationRequestHolder(authorizationRequest, - authentication); + OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest); + + OAuth2Authentication combinedAuth = new OAuth2Authentication(storedOAuth2Request, authentication); String code = authorizationCodeServices.createAuthorizationCode(combinedAuth); return code; @@ -345,28 +460,15 @@ private String getSuccessfulRedirect(AuthorizationRequest authorizationRequest, throw new IllegalStateException("No authorization code found in the current request scope."); } - String requestedRedirect = authorizationRequest.getRedirectUri(); - String[] fragments = requestedRedirect.split("#"); - String state = authorizationRequest.getState(); - - StringBuilder url = new StringBuilder(fragments[0]); - if (requestedRedirect.indexOf('?') < 0) { - url.append('?'); - } - else { - url.append('&'); - } - url.append("code=").append(authorizationCode); + Map query = new LinkedHashMap(); + query.put("code", authorizationCode); + String state = authorizationRequest.getState(); if (state != null) { - url.append("&state=").append(state); - } - - if (fragments.length > 1) { - url.append("#" + fragments[1]); + query.put("state", state); } - return url.toString(); + return append(authorizationRequest.getRedirectUri(), query, false); } private String getUnsuccessfulRedirect(AuthorizationRequest authorizationRequest, OAuth2Exception failure, @@ -377,46 +479,82 @@ private String getUnsuccessfulRedirect(AuthorizationRequest authorizationRequest throw new UnapprovedClientAuthenticationException("Authorization failure, and no redirect URI.", failure); } - String redirectUri = authorizationRequest.getRedirectUri(); + Map query = new LinkedHashMap(); - // extract existing fragments if any - String[] fragments = redirectUri.split("#"); + query.put("error", failure.getOAuth2ErrorCode()); + query.put("error_description", failure.getMessage()); - StringBuilder url = new StringBuilder(fragment ? redirectUri : fragments[0]); - - char separator = fragment ? '#' : '?'; - if (redirectUri.indexOf(separator) < 0) { - url.append(separator); + if (authorizationRequest.getState() != null) { + query.put("state", authorizationRequest.getState()); } - else { - url.append('&'); + + if (failure.getAdditionalInformation() != null) { + for (Map.Entry additionalInfo : failure.getAdditionalInformation().entrySet()) { + query.put(additionalInfo.getKey(), additionalInfo.getValue()); + } } - url.append("error=").append(failure.getOAuth2ErrorCode()); - try { - url.append("&error_description=").append(URLEncoder.encode(failure.getMessage(), "UTF-8")); + return append(authorizationRequest.getRedirectUri(), query, fragment); - if (authorizationRequest.getState() != null) { - url.append('&').append("state=").append(authorizationRequest.getState()); - } + } - if (failure.getAdditionalInformation() != null) { - for (Map.Entry additionalInfo : failure.getAdditionalInformation().entrySet()) { - url.append('&').append(additionalInfo.getKey()).append('=') - .append(URLEncoder.encode(additionalInfo.getValue(), "UTF-8")); - } - } + private String append(String base, Map query, boolean fragment) { + return append(base, query, null, fragment); + } + + private String append(String base, Map query, Map keys, boolean fragment) { + UriComponentsBuilder template = UriComponentsBuilder.newInstance(); + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base); + URI redirectUri; + try { + // assume it's encoded to start with (if it came in over the wire) + redirectUri = builder.build(true).toUri(); } - catch (UnsupportedEncodingException e) { - throw new IllegalStateException(e); + catch (Exception e) { + // ... but allow client registrations to contain hard-coded non-encoded values + redirectUri = builder.build().toUri(); + builder = UriComponentsBuilder.fromUri(redirectUri); + } + template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost()) + .userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath()); + + if (fragment) { + StringBuilder values = new StringBuilder(); + if (redirectUri.getFragment() != null) { + String append = redirectUri.getFragment(); + values.append(append); + } + for (String key : query.keySet()) { + if (values.length() > 0) { + values.append("&"); + } + String name = key; + if (keys != null && keys.containsKey(key)) { + name = keys.get(key); + } + values.append(name + "={" + key + "}"); + } + if (values.length() > 0) { + template.fragment(values.toString()); + } + UriComponents encoded = template.build().expand(query).encode(); + builder.fragment(encoded.getFragment()); } - - if (!fragment && fragments.length > 1) { - url.append("#" + fragments[1]); + else { + for (String key : query.keySet()) { + String name = key; + if (keys != null && keys.containsKey(key)) { + name = keys.get(key); + } + template.queryParam(name, "{" + key + "}"); + } + template.fragment(redirectUri.getFragment()); + UriComponents encoded = template.build().expand(query).encode(); + builder.query(encoded.getQuery()); } - return url.toString(); + return builder.build().toUriString(); } @@ -436,9 +574,18 @@ public void setUserApprovalHandler(UserApprovalHandler userApprovalHandler) { this.userApprovalHandler = userApprovalHandler; } - @ExceptionHandler(NoSuchClientException.class) - public ModelAndView handleNoSuchClientException(Exception e, ServletWebRequest webRequest) throws Exception { - logger.info("Handling NoSuchClientException error: " + e.getMessage()); + public void setOAuth2RequestValidator(OAuth2RequestValidator oauth2RequestValidator) { + this.oauth2RequestValidator = oauth2RequestValidator; + } + + @SuppressWarnings("deprecation") + public void setImplicitGrantService( + org.springframework.security.oauth2.provider.implicit.ImplicitGrantService implicitGrantService) { + } + + @ExceptionHandler(ClientRegistrationException.class) + public ModelAndView handleClientRegistrationException(Exception e, ServletWebRequest webRequest) throws Exception { + logger.info("Handling ClientRegistrationException error: " + e.getMessage()); return handleException(new BadClientCredentialsException(), webRequest); } @@ -465,17 +612,18 @@ private ModelAndView handleException(Exception e, ServletWebRequest webRequest) return new ModelAndView(errorPage, Collections.singletonMap("error", translate.getBody())); } - AuthorizationRequest errorRequest = null; + AuthorizationRequest authorizationRequest = null; try { - errorRequest = getAuthorizationRequestForError(webRequest); - DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest(errorRequest); - String requestedRedirectParam = authorizationRequest.getAuthorizationParameters().get(REDIRECT_URI); + authorizationRequest = getAuthorizationRequestForError(webRequest); + String requestedRedirectParam = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI); String requestedRedirect = redirectResolver.resolveRedirect(requestedRedirectParam, getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId())); authorizationRequest.setRedirectUri(requestedRedirect); String redirect = getUnsuccessfulRedirect(authorizationRequest, translate.getBody(), authorizationRequest .getResponseTypes().contains("token")); - return new ModelAndView(new RedirectView(redirect, false)); + RedirectView redirectView = new RedirectView(redirect, false, true, false); + redirectView.setStatusCode(HttpStatus.SEE_OTHER); + return new ModelAndView(redirectView); } catch (OAuth2Exception ex) { // If an AuthorizationRequest cannot be created from the incoming parameters it must be @@ -490,7 +638,7 @@ private AuthorizationRequest getAuthorizationRequestForError(ServletWebRequest w // If it's already there then we are in the approveOrDeny phase and we can use the saved request AuthorizationRequest authorizationRequest = (AuthorizationRequest) sessionAttributeStore.retrieveAttribute( - webRequest, "authorizationRequest"); + webRequest, AUTHORIZATION_REQUEST_ATTR_NAME); if (authorizationRequest != null) { return authorizationRequest; } @@ -505,11 +653,20 @@ private AuthorizationRequest getAuthorizationRequestForError(ServletWebRequest w } try { - return getAuthorizationRequestManager().createAuthorizationRequest(parameters); + return getOAuth2RequestFactory().createAuthorizationRequest(parameters); } catch (Exception e) { - return getDefaultAuthorizationRequestManager().createAuthorizationRequest(parameters); + return getDefaultOAuth2RequestFactory().createAuthorizationRequest(parameters); } } + + private void setCacheControlHeaders() { + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (servletRequestAttributes != null) { + HttpServletResponse servletResponse = servletRequestAttributes.getResponse(); + servletResponse.setHeader(HttpHeaders.CACHE_CONTROL, "no-store"); + servletResponse.setHeader(HttpHeaders.PRAGMA, "no-cache"); + } + } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/CheckTokenEndpoint.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/CheckTokenEndpoint.java new file mode 100644 index 000000000..218520c06 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/CheckTokenEndpoint.java @@ -0,0 +1,117 @@ +/* + * Copyright 2009-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.provider.endpoint; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator; +import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; +import org.springframework.security.oauth2.provider.token.AccessTokenConverter; +import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.Map; + +/** + * Controller which decodes access tokens for clients who are not able to do so (or where opaque token values are used). + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Luke Taylor + * @author Joel D'sa + */ +@FrameworkEndpoint +@Deprecated +public class CheckTokenEndpoint { + + private ResourceServerTokenServices resourceServerTokenServices; + + private AccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter(); + + protected final Log logger = LogFactory.getLog(getClass()); + + private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator(); + + public CheckTokenEndpoint(ResourceServerTokenServices resourceServerTokenServices) { + this.resourceServerTokenServices = resourceServerTokenServices; + } + + /** + * @param exceptionTranslator the exception translator to set + */ + public void setExceptionTranslator(WebResponseExceptionTranslator exceptionTranslator) { + this.exceptionTranslator = exceptionTranslator; + } + + /** + * @param accessTokenConverter the accessTokenConverter to set + */ + public void setAccessTokenConverter(AccessTokenConverter accessTokenConverter) { + this.accessTokenConverter = accessTokenConverter; + } + + @RequestMapping(value = "/oauth/check_token", method = RequestMethod.POST) + @ResponseBody + public Map checkToken(@RequestParam("token") String value) { + + OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value); + if (token == null) { + throw new InvalidTokenException("Token was not recognised"); + } + + if (token.isExpired()) { + throw new InvalidTokenException("Token has expired"); + } + + OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue()); + + Map response = (Map)accessTokenConverter.convertAccessToken(token, authentication); + + // gh-1070 + response.put("active", true); // Always true if token exists and not expired + + return response; + } + + @ExceptionHandler(InvalidTokenException.class) + public ResponseEntity handleException(Exception e) throws Exception { + logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage()); + // This isn't an oauth resource, so we don't want to send an + // unauthorized code here. The client has already authenticated + // successfully with basic auth and should just + // get back the invalid token error. + @SuppressWarnings("serial") + InvalidTokenException e400 = new InvalidTokenException(e.getMessage()) { + @Override + public int getHttpErrorCode() { + return 400; + } + }; + return exceptionTranslator.translate(e400); + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/DefaultRedirectResolver.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/DefaultRedirectResolver.java index 5124c1860..8cf102925 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/DefaultRedirectResolver.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/DefaultRedirectResolver.java @@ -1,11 +1,11 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -15,28 +15,60 @@ */ package org.springframework.security.oauth2.provider.endpoint; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.util.Assert; +import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; /** * Default implementation for a redirect resolver. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class DefaultRedirectResolver implements RedirectResolver { - + private Collection redirectGrantTypes = Arrays.asList("implicit", "authorization_code"); - + + private boolean matchSubdomains = false; + + private boolean matchPorts = true; + + /** + * Flag to indicate that requested URIs will match if they are a subdomain of the registered value. + * + * @param matchSubdomains the flag value to set (default true) + */ + public void setMatchSubdomains(boolean matchSubdomains) { + this.matchSubdomains = matchSubdomains; + } + + /** + * Flag that enables/disables port matching between the requested redirect URI and the registered redirect URI(s). + * + * @param matchPorts true to enable port matching, false to disable (defaults to true) + */ + public void setMatchPorts(boolean matchPorts) { + this.matchPorts = matchPorts; + } + /** * Grant types that are permitted to have a redirect uri. * @@ -47,27 +79,21 @@ public void setRedirectGrantTypes(Collection redirectGrantTypes) { } public String resolveRedirect(String requestedRedirect, ClientDetails client) throws OAuth2Exception { - + Set authorizedGrantTypes = client.getAuthorizedGrantTypes(); if (authorizedGrantTypes.isEmpty()) { - throw new InvalidGrantException("A client must have at least one authorized grant type."); + throw new InvalidGrantException("A client must have at least one authorized grant type."); } if (!containsRedirectGrantType(authorizedGrantTypes)) { - throw new InvalidGrantException("A redirect_uri can only be used by implicit or authorization_code grant types."); + throw new InvalidGrantException( + "A redirect_uri can only be used by implicit or authorization_code grant types."); } - Set redirectUris = client.getRegisteredRedirectUri(); - - if (redirectUris != null && !redirectUris.isEmpty()) { - return obtainMatchingRedirect(redirectUris, requestedRedirect); + Set registeredRedirectUris = client.getRegisteredRedirectUri(); + if (registeredRedirectUris == null || registeredRedirectUris.isEmpty()) { + throw new InvalidRequestException("At least one redirect_uri must be registered with the client."); } - else if (StringUtils.hasText(requestedRedirect)) { - return requestedRedirect; - } - else { - throw new RedirectMismatchException("A redirect_uri must be supplied."); - } - + return obtainMatchingRedirect(registeredRedirectUris, requestedRedirect); } /** @@ -84,16 +110,90 @@ private boolean containsRedirectGrantType(Set grantTypes) { } /** - * Whether the requested redirect URI "matches" the specified redirect URI. This implementation tests if the user - * requrested redirect starts with the registered redirect, so it would have the same host and root path if it is an - * HTTP URL. + * Whether the requested redirect URI "matches" the specified redirect URI. For a URL, this implementation tests if + * the user requested redirect starts with the registered redirect, so it would have the same host and root path if + * it is an HTTP URL. The port, userinfo, query params also matched. Request redirect uri path can include + * additional parameters which are ignored for the match + *

+ * For other (non-URL) cases, such as for some implicit clients, the redirect_uri must be an exact match. * * @param requestedRedirect The requested redirect URI. * @param redirectUri The registered redirect URI. * @return Whether the requested redirect URI "matches" the specified redirect URI. */ protected boolean redirectMatches(String requestedRedirect, String redirectUri) { - return requestedRedirect.startsWith(redirectUri); + UriComponents requestedRedirectUri = UriComponentsBuilder.fromUriString(requestedRedirect).build(); + UriComponents registeredRedirectUri = UriComponentsBuilder.fromUriString(redirectUri).build(); + + boolean schemeMatch = isEqual(registeredRedirectUri.getScheme(), requestedRedirectUri.getScheme()); + boolean userInfoMatch = isEqual(registeredRedirectUri.getUserInfo(), requestedRedirectUri.getUserInfo()); + boolean hostMatch = hostMatches(registeredRedirectUri.getHost(), requestedRedirectUri.getHost()); + boolean portMatch = matchPorts ? registeredRedirectUri.getPort() == requestedRedirectUri.getPort() : true; + boolean pathMatch = isEqual(registeredRedirectUri.getPath(), + StringUtils.cleanPath(requestedRedirectUri.getPath())); + boolean queryParamMatch = matchQueryParams(registeredRedirectUri.getQueryParams(), + requestedRedirectUri.getQueryParams()); + + return schemeMatch && userInfoMatch && hostMatch && portMatch && pathMatch && queryParamMatch; + } + + + /** + * Checks whether the registered redirect uri query params key and values contains match the requested set + * + * The requested redirect uri query params are allowed to contain additional params which will be retained + * + * @param registeredRedirectUriQueryParams + * @param requestedRedirectUriQueryParams + * @return whether the params match + */ + private boolean matchQueryParams(MultiValueMap registeredRedirectUriQueryParams, + MultiValueMap requestedRedirectUriQueryParams) { + + + Iterator iter = registeredRedirectUriQueryParams.keySet().iterator(); + while (iter.hasNext()) { + String key = iter.next(); + List registeredRedirectUriQueryParamsValues = registeredRedirectUriQueryParams.get(key); + List requestedRedirectUriQueryParamsValues = requestedRedirectUriQueryParams.get(key); + + if (!registeredRedirectUriQueryParamsValues.equals(requestedRedirectUriQueryParamsValues)) { + return false; + } + } + + return true; + } + + + + /** + * Compares two strings but treats empty string or null equal + * + * @param str1 + * @param str2 + * @return true if strings are equal, false otherwise + */ + private boolean isEqual(String str1, String str2) { + if (StringUtils.isEmpty(str1)) { + return StringUtils.isEmpty(str2); + } else { + return str1.equals(str2); + } + } + + /** + * Check if host matches the registered value. + * + * @param registered the registered host. Can be null. + * @param requested the requested host. Can be null. + * @return true if they match + */ + protected boolean hostMatches(String registered, String requested) { + if (matchSubdomains) { + return isEqual(registered, requested) || (requested != null && requested.endsWith("." + registered)); + } + return isEqual(registered, requested); } /** @@ -101,7 +201,7 @@ protected boolean redirectMatches(String requestedRedirect, String redirectUri) * * @param redirectUris the set of the registered URIs to try and find a match. This cannot be null or empty. * @param requestedRedirect the URI used as part of the request - * @return the matching URI + * @return redirect uri * @throws RedirectMismatchException if no match was found */ private String obtainMatchingRedirect(Set redirectUris, String requestedRedirect) { @@ -110,12 +210,26 @@ private String obtainMatchingRedirect(Set redirectUris, String requested if (redirectUris.size() == 1 && requestedRedirect == null) { return redirectUris.iterator().next(); } + for (String redirectUri : redirectUris) { if (requestedRedirect != null && redirectMatches(requestedRedirect, redirectUri)) { - return requestedRedirect; + // Initialize with the registered redirect-uri + UriComponentsBuilder redirectUriBuilder = UriComponentsBuilder.fromUriString(redirectUri); + + UriComponents requestedRedirectUri = UriComponentsBuilder.fromUriString(requestedRedirect).build(); + + if (this.matchSubdomains) { + redirectUriBuilder.host(requestedRedirectUri.getHost()); + } + if (!this.matchPorts) { + redirectUriBuilder.port(requestedRedirectUri.getPort()); + } + redirectUriBuilder.replaceQuery(requestedRedirectUri.getQuery()); // retain additional params (if any) + redirectUriBuilder.fragment(null); + return redirectUriBuilder.build().toUriString(); } } - throw new RedirectMismatchException("Invalid redirect: " + requestedRedirect - + " does not match one of the registered values: " + redirectUris.toString()); + + throw new RedirectMismatchException("Invalid redirect uri does not match one of the registered values."); } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/ExactMatchRedirectResolver.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/ExactMatchRedirectResolver.java index b4ba8c9fc..a77dced83 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/ExactMatchRedirectResolver.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/ExactMatchRedirectResolver.java @@ -3,17 +3,22 @@ /** - * Default implementation for a redirect resolver. - * + * Strict implementation for a redirect resolver which requires + * an exact match between the registered and requested redirect_uri. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public class ExactMatchRedirectResolver extends DefaultRedirectResolver { /** * Whether the requested redirect URI "matches" the specified redirect URI. This implementation tests strict * equality. - * + * * @param requestedRedirect The requested redirect URI. * @param redirectUri The registered redirect URI. * @return Whether the requested redirect URI "matches" the specified redirect URI. diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/FrameworkEndpoint.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/FrameworkEndpoint.java index ea5759aa1..328813117 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/FrameworkEndpoint.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/FrameworkEndpoint.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -30,13 +30,17 @@ * Users of the Spring Security OAuth2 XSD namespace need not use this feature explicitly as the relevant handlers will * be registered by the parsers. *

- * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ @Component @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@Deprecated public @interface FrameworkEndpoint { } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/FrameworkEndpointHandlerMapping.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/FrameworkEndpointHandlerMapping.java index 48158f584..ea82fe2e6 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/FrameworkEndpointHandlerMapping.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/FrameworkEndpointHandlerMapping.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -15,43 +15,104 @@ import java.lang.reflect.Method; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.util.StringUtils; import org.springframework.web.servlet.mvc.condition.NameValueExpression; import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.servlet.view.UrlBasedViewResolver; /** * A handler mapping for framework endpoints (those annotated with @FrameworkEndpoint). - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class FrameworkEndpointHandlerMapping extends RequestMappingHandlerMapping { + private static final String REDIRECT = UrlBasedViewResolver.REDIRECT_URL_PREFIX; + + private static final String FORWARD = UrlBasedViewResolver.FORWARD_URL_PREFIX; + private Map mappings = new HashMap(); - private String approvalParameter = AuthorizationRequest.USER_OAUTH_APPROVAL; + private String approvalParameter = OAuth2Utils.USER_OAUTH_APPROVAL; + + private Set paths = new HashSet(); + + private String prefix; + + /** + * @param prefix the prefix to set + */ + public void setPrefix(String prefix) { + if (!StringUtils.hasText(prefix)) { + prefix = ""; + } + else + while (prefix.endsWith("/")) { + prefix = prefix.substring(0, prefix.lastIndexOf("/")); + } + this.prefix = prefix; + } /** * Custom mappings for framework endpoint paths. The keys in the map are the default framework endpoint path, e.g. * "/oauth/authorize", and the values are the desired runtime paths. * - * @param mappings the mappings to set + * @param patternMap the mappings to set */ public void setMappings(Map patternMap) { - this.mappings = patternMap; + this.mappings = new HashMap(patternMap); + for (String key : mappings.keySet()) { + String result = mappings.get(key); + if (result.startsWith(FORWARD)) { + result = result.substring(FORWARD.length()); + } + if (result.startsWith(REDIRECT)) { + result = result.substring(REDIRECT.length()); + } + mappings.put(key, result); + } + } + + /** + * @return the mapping from default endpoint paths to custom ones (or the default if no customization is known) + */ + public String getServletPath(String defaultPath) { + return (prefix == null ? "" : prefix) + getPath(defaultPath); + } + + /** + * @return the mapping from default endpoint paths to custom ones (or the default if no customization is known) + */ + public String getPath(String defaultPath) { + String result = defaultPath; + if (mappings.containsKey(defaultPath)) { + result = mappings.get(defaultPath); + } + return result; + } + + public Set getPaths() { + return paths; } /** * The name of the request parameter that distinguishes a call to approve an authorization. Default is - * {@link AuthorizationRequest#USER_OAUTH_APPROVAL}. + * {@link OAuth2Utils#USER_OAUTH_APPROVAL}. * * @param approvalParameter the approvalParameter to set */ @@ -61,7 +122,7 @@ public void setApprovalParameter(String approvalParameter) { public FrameworkEndpointHandlerMapping() { // Make sure user-supplied mappings take precedence by default (except the resource mapping) - setOrder(Ordered.LOWEST_PRECEDENCE - 1); + setOrder(Ordered.LOWEST_PRECEDENCE - 2); } /** @@ -87,25 +148,24 @@ protected RequestMappingInfo getMappingForMethod(Method method, Class handler int i = 0; for (String pattern : defaultPatterns) { - patterns[i] = pattern; - if (mappings.containsKey(pattern)) { - patterns[i] = mappings.get(pattern); - } + patterns[i] = getPath(pattern); + paths.add(pattern); i++; } - PatternsRequestCondition patternsInfo = new PatternsRequestCondition(patterns); + PatternsRequestCondition patternsInfo = new PatternsRequestCondition(patterns, getUrlPathHelper(), + getPathMatcher(), useSuffixPatternMatch(), useTrailingSlashMatch(), getFileExtensions()); ParamsRequestCondition paramsInfo = defaultMapping.getParamsCondition(); - if (!approvalParameter.equals(AuthorizationRequest.USER_OAUTH_APPROVAL) - && defaultPatterns.contains("/oauth/authorize")) { + if (!approvalParameter.equals(OAuth2Utils.USER_OAUTH_APPROVAL) && defaultPatterns.contains("/oauth/authorize")) { String[] params = new String[paramsInfo.getExpressions().size()]; Set> expressions = paramsInfo.getExpressions(); i = 0; for (NameValueExpression expression : expressions) { String param = expression.toString(); - if (AuthorizationRequest.USER_OAUTH_APPROVAL.equals(param)) { + if (OAuth2Utils.USER_OAUTH_APPROVAL.equals(param)) { params[i] = approvalParameter; - } else { + } + else { params[i] = param; } i++; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/RedirectResolver.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/RedirectResolver.java index b4869f960..c80c4a321 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/RedirectResolver.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/RedirectResolver.java @@ -5,9 +5,13 @@ /** * Basic interface for determining the redirect URI for a user agent. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton */ +@Deprecated public interface RedirectResolver { /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/TokenEndpoint.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/TokenEndpoint.java index 2afa4221c..6c6cb7793 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/TokenEndpoint.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/TokenEndpoint.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,29 +16,40 @@ package org.springframework.security.oauth2.provider.endpoint; -import java.security.Principal; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.BadClientCredentialsException; +import org.springframework.security.oauth2.common.exceptions.InvalidClientException; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException; import org.springframework.security.oauth2.common.util.OAuth2Utils; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; -import org.springframework.security.oauth2.provider.NoSuchClientException; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientRegistrationException; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2RequestValidator; +import org.springframework.security.oauth2.provider.TokenRequest; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator; import org.springframework.util.StringUtils; +import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; +import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + /** *

* Endpoint for token requests as described in the OAuth2 spec. Clients post requests with a grant_type @@ -52,69 +63,131 @@ * id is extracted from the authentication token. The best way to arrange this (as per the OAuth2 spec) is to use HTTP * basic authentication for this endpoint with standard Spring Security support. *

- * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ @FrameworkEndpoint -@RequestMapping(value = "/oauth/token") +@Deprecated public class TokenEndpoint extends AbstractEndpoint { - @RequestMapping - public ResponseEntity getAccessToken(Principal principal, - @RequestParam(value = "grant_type", required = false) String grantType, - @RequestParam Map parameters) { + private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator(); + + private Set allowedRequestMethods = new HashSet(Arrays.asList(HttpMethod.POST)); + + @RequestMapping(value = "/oauth/token", method=RequestMethod.GET) + public ResponseEntity getAccessToken( + Principal principal, @RequestParam Map parameters) + throws HttpRequestMethodNotSupportedException { + + if (!allowedRequestMethods.contains(HttpMethod.GET)) { + throw new HttpRequestMethodNotSupportedException("GET"); + } + return postAccessToken(principal, parameters); + } + + @RequestMapping(value = "/oauth/token", method=RequestMethod.POST) + public ResponseEntity postAccessToken( + Principal principal, @RequestParam Map parameters) + throws HttpRequestMethodNotSupportedException { if (!(principal instanceof Authentication)) { throw new InsufficientAuthenticationException( "There is no client authentication. Try adding an appropriate authentication filter."); } - Authentication client = (Authentication) principal; - if (!client.isAuthenticated()) { - throw new InsufficientAuthenticationException("The client is not authenticated."); + String clientId = getClientId(principal); + ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId); + + TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient); + + // Only validate client details if a client is authenticated during this request. + // Double check to make sure that the client ID is the same in the token request and authenticated client. + if (StringUtils.hasText(clientId) && !clientId.equals(tokenRequest.getClientId())) { + throw new InvalidClientException("Given client ID does not match authenticated client"); } - HashMap request = new HashMap(parameters); - String clientId = client.getName(); - request.put("client_id", clientId); - if (!StringUtils.hasText(grantType)) { + if (authenticatedClient != null) { + oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient); + } + + if (!StringUtils.hasText(tokenRequest.getGrantType())) { throw new InvalidRequestException("Missing grant type"); } - getAuthorizationRequestManager().validateParameters(parameters, - getClientDetailsService().loadClientByClientId(clientId)); + if (tokenRequest.getGrantType().equals("implicit")) { + throw new InvalidGrantException("Implicit grant type not supported from token endpoint"); + } - DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest( - getAuthorizationRequestManager().createAuthorizationRequest(request)); - if (isAuthCodeRequest(parameters) || isRefreshTokenRequest(parameters)) { + if (isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) { // The scope was requested or determined during the authorization step - if (!authorizationRequest.getScope().isEmpty()) { - logger.debug("Clearing scope of incoming auth code request"); - authorizationRequest.setScope(Collections. emptySet()); + logger.debug("Clearing scope of incoming token request"); + tokenRequest.setScope(Collections.emptySet()); + } else if (isRefreshTokenRequest(parameters)) { + if (StringUtils.isEmpty(parameters.get("refresh_token"))) { + throw new InvalidRequestException("refresh_token parameter not provided"); } - } - if (isRefreshTokenRequest(parameters)) { // A refresh token has its own default scopes, so we should ignore any added by the factory here. - authorizationRequest.setScope(OAuth2Utils.parseParameterList(parameters.get("scope"))); + tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE))); } - OAuth2AccessToken token = getTokenGranter().grant(grantType, authorizationRequest); + + OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest); if (token == null) { - throw new UnsupportedGrantTypeException("Unsupported grant type: " + grantType); + throw new UnsupportedGrantTypeException("Unsupported grant type"); } return getResponse(token); + } + /** + * @param principal the currently authentication principal + * @return a client id if there is one in the principal + */ + protected String getClientId(Principal principal) { + Authentication client = (Authentication) principal; + if (!client.isAuthenticated()) { + throw new InsufficientAuthenticationException("The client is not authenticated."); + } + String clientId = client.getName(); + if (client instanceof OAuth2Authentication) { + // Might be a client and user combined authentication + clientId = ((OAuth2Authentication) client).getOAuth2Request().getClientId(); + } + return clientId; } - @ExceptionHandler(NoSuchClientException.class) - public ResponseEntity handleNoSuchClientException(Exception e) throws Exception { - return handleException(new BadClientCredentialsException()); + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public ResponseEntity handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) throws Exception { + if (logger.isInfoEnabled()) { + logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage()); + } + return getExceptionTranslator().translate(e); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) throws Exception { + if (logger.isErrorEnabled()) { + logger.error("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage(), e); + } + return getExceptionTranslator().translate(e); + } + + @ExceptionHandler(ClientRegistrationException.class) + public ResponseEntity handleClientRegistrationException(Exception e) throws Exception { + if (logger.isWarnEnabled()) { + logger.warn("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage()); + } + return getExceptionTranslator().translate(new BadClientCredentialsException()); } @ExceptionHandler(OAuth2Exception.class) - public ResponseEntity handleException(Exception e) throws Exception { - logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage()); + public ResponseEntity handleException(OAuth2Exception e) throws Exception { + if (logger.isWarnEnabled()) { + logger.warn("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage()); + } return getExceptionTranslator().translate(e); } @@ -122,15 +195,23 @@ private ResponseEntity getResponse(OAuth2AccessToken accessTo HttpHeaders headers = new HttpHeaders(); headers.set("Cache-Control", "no-store"); headers.set("Pragma", "no-cache"); + headers.set("Content-Type", "application/json;charset=UTF-8"); return new ResponseEntity(accessToken, headers, HttpStatus.OK); } private boolean isRefreshTokenRequest(Map parameters) { - return "refresh_token".equals(parameters.get("grant_type")) && parameters.get("refresh_token") != null; + return "refresh_token".equals(parameters.get("grant_type")); } private boolean isAuthCodeRequest(Map parameters) { - return "authorization_code".equals(parameters.get("grant_type")) && parameters.get("code") != null; + return "authorization_code".equals(parameters.get(OAuth2Utils.GRANT_TYPE)) && parameters.get("code") != null; } + public void setOAuth2RequestValidator(OAuth2RequestValidator oAuth2RequestValidator) { + this.oAuth2RequestValidator = oAuth2RequestValidator; + } + + public void setAllowedRequestMethods(Set allowedRequestMethods) { + this.allowedRequestMethods = allowedRequestMethods; + } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/TokenEndpointAuthenticationFilter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/TokenEndpointAuthenticationFilter.java new file mode 100644 index 000000000..0b86a1961 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/TokenEndpointAuthenticationFilter.java @@ -0,0 +1,230 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.endpoint; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.authentication.AuthenticationDetailsSource; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; + +/** + *

+ * An optional authentication filter for the {@link TokenEndpoint}. It sits downstream of another filter (usually + * {@link BasicAuthenticationFilter}) for the client, and creates an {@link OAuth2Authentication} for the Spring + * {@link SecurityContext} if the request also contains user credentials, e.g. as typically would be the case in a + * password grant. This filter is only required if the TokenEndpoint (or one of it's dependencies) needs to know about + * the authenticated user. In a vanilla password grant this isn't normally necessary because the token granter + * will also authenticate the user. + *

+ * + *

+ * If this filter is used the Spring Security context will contain an OAuth2Authentication encapsulating (as the + * authorization request) the form parameters coming into the filter and the client id from the already authenticated + * client authentication, and the authenticated user token extracted from the request and validated using the + * authentication manager. + *

+ * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class TokenEndpointAuthenticationFilter implements Filter { + + private static final Log logger = LogFactory.getLog(TokenEndpointAuthenticationFilter.class); + + private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); + + private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint(); + + private final AuthenticationManager authenticationManager; + + private final OAuth2RequestFactory oAuth2RequestFactory; + + /** + * @param authenticationManager an AuthenticationManager for the incoming request + */ + public TokenEndpointAuthenticationFilter(AuthenticationManager authenticationManager, OAuth2RequestFactory oAuth2RequestFactory) { + super(); + this.authenticationManager = authenticationManager; + this.oAuth2RequestFactory = oAuth2RequestFactory; + } + + /** + * An authentication entry point that can handle unsuccessful authentication. Defaults to an + * {@link OAuth2AuthenticationEntryPoint}. + * + * @param authenticationEntryPoint the authenticationEntryPoint to set + */ + public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { + this.authenticationEntryPoint = authenticationEntryPoint; + } + + /** + * A source of authentication details for requests that result in authentication. + * + * @param authenticationDetailsSource the authenticationDetailsSource to set + */ + public void setAuthenticationDetailsSource( + AuthenticationDetailsSource authenticationDetailsSource) { + this.authenticationDetailsSource = authenticationDetailsSource; + } + + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, + ServletException { + + final boolean debug = logger.isDebugEnabled(); + final HttpServletRequest request = (HttpServletRequest) req; + final HttpServletResponse response = (HttpServletResponse) res; + + try { + Authentication credentials = extractCredentials(request); + + if (credentials != null) { + + if (debug) { + logger.debug("Authentication credentials found"); + } + + Authentication authResult = authenticationManager.authenticate(credentials); + + if (debug) { + logger.debug("Authentication success: " + authResult.getName()); + } + + Authentication clientAuth = SecurityContextHolder.getContext().getAuthentication(); + if (clientAuth == null) { + throw new BadCredentialsException( + "No client authentication found. Remember to put a filter upstream of the TokenEndpointAuthenticationFilter."); + } + + Map map = getSingleValueMap(request); + map.put(OAuth2Utils.CLIENT_ID, clientAuth.getName()); + AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(map); + + authorizationRequest.setScope(getScope(request)); + if (clientAuth.isAuthenticated()) { + // Ensure the OAuth2Authentication is authenticated + authorizationRequest.setApproved(true); + } + + OAuth2Request storedOAuth2Request = oAuth2RequestFactory.createOAuth2Request(authorizationRequest); + + SecurityContextHolder.getContext().setAuthentication( + new OAuth2Authentication(storedOAuth2Request, authResult)); + + onSuccessfulAuthentication(request, response, authResult); + + } + + } + catch (AuthenticationException failed) { + SecurityContextHolder.clearContext(); + + if (debug) { + logger.debug("Authentication request for failed: " + failed); + } + + onUnsuccessfulAuthentication(request, response, failed); + + authenticationEntryPoint.commence(request, response, failed); + + return; + } + + chain.doFilter(request, response); + } + + private Map getSingleValueMap(HttpServletRequest request) { + Map map = new HashMap(); + Map parameters = request.getParameterMap(); + for (String key : parameters.keySet()) { + String[] values = parameters.get(key); + map.put(key, values != null && values.length > 0 ? values[0] : null); + } + return map; + } + + protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, + Authentication authResult) throws IOException { + } + + protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, + AuthenticationException failed) throws IOException { + } + + /** + * If the incoming request contains user credentials in headers or parameters then extract them here into an + * Authentication token that can be validated later. This implementation only recognises password grant requests and + * extracts the username and password. + * + * @param request the incoming request, possibly with user credentials + * @return an authentication for validation (or null if there is no further authentication) + */ + protected Authentication extractCredentials(HttpServletRequest request) { + String grantType = request.getParameter(OAuth2Utils.GRANT_TYPE); + if (grantType != null && grantType.equals("password")) { + UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken( + request.getParameter("username"), request.getParameter("password")); + result.setDetails(authenticationDetailsSource.buildDetails(request)); + return result; + } + return null; + } + + private Set getScope(HttpServletRequest request) { + return OAuth2Utils.parseParameterList(request.getParameter(OAuth2Utils.SCOPE)); + } + + public void init(FilterConfig filterConfig) throws ServletException { + } + + public void destroy() { + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/TokenKeyEndpoint.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/TokenKeyEndpoint.java new file mode 100644 index 000000000..f3dd2f500 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/TokenKeyEndpoint.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + *******************************************************************************/ +package org.springframework.security.oauth2.provider.endpoint; + +import java.security.Principal; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * OAuth2 token services that produces JWT encoded token values. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * @author Luke Taylor + * @author Joel D'sa + */ +@FrameworkEndpoint +@Deprecated +public class TokenKeyEndpoint { + + protected final Log logger = LogFactory.getLog(getClass()); + + private JwtAccessTokenConverter converter; + + public TokenKeyEndpoint(JwtAccessTokenConverter converter) { + super(); + this.converter = converter; + } + + /** + * Get the verification key for the token signatures. The principal has to + * be provided only if the key is secret + * (shared not public). + * + * @param principal the currently authenticated user if there is one + * @return the key used to verify tokens + */ + @RequestMapping(value = "/oauth/token_key", method = RequestMethod.GET) + @ResponseBody + public Map getKey(Principal principal) { + if ((principal == null || principal instanceof AnonymousAuthenticationToken) && !converter.isPublic()) { + throw new AccessDeniedException("You need to authenticate to see a shared key"); + } + Map result = converter.getKey(); + return result; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/WhitelabelApprovalEndpoint.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/WhitelabelApprovalEndpoint.java index 989398c64..f05a9b464 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/WhitelabelApprovalEndpoint.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/WhitelabelApprovalEndpoint.java @@ -1,93 +1,118 @@ package org.springframework.security.oauth2.provider.endpoint; -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.context.expression.MapAccessor; -import org.springframework.expression.Expression; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.util.PropertyPlaceholderHelper; -import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.web.csrf.CsrfToken; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.View; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.web.util.HtmlUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; /** - * Controller for displaying the approval and error pages for the authorization server. - * + * Controller for displaying the approval page for the authorization server. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer */ @FrameworkEndpoint @SessionAttributes("authorizationRequest") +@Deprecated public class WhitelabelApprovalEndpoint { @RequestMapping("/oauth/confirm_access") - public ModelAndView getAccessConfirmation(Map model) throws Exception { - return new ModelAndView(new SpelView(APPROVAL), model); - } - - @RequestMapping("/oauth/error") - public ModelAndView handleError(HttpServletRequest request) { - Map model = new HashMap(); - model.put("error", request.getAttribute("error")); - return new ModelAndView(new SpelView(ERROR), model); + public ModelAndView getAccessConfirmation(Map model, HttpServletRequest request) throws Exception { + final String approvalContent = createTemplate(model, request); + if (request.getAttribute("_csrf") != null) { + model.put("_csrf", request.getAttribute("_csrf")); + } + View approvalView = new View() { + @Override + public String getContentType() { + return "text/html"; + } + + @Override + public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { + response.setContentType(getContentType()); + response.getWriter().append(approvalContent); + } + }; + return new ModelAndView(approvalView, model); } - /** - * Simple String template renderer. - * - */ - private static class SpelView implements View { - - private final String template; - - private final SpelExpressionParser parser = new SpelExpressionParser(); + protected String createTemplate(Map model, HttpServletRequest request) { + AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest"); + String clientId = authorizationRequest.getClientId(); - private final StandardEvaluationContext context = new StandardEvaluationContext(); + StringBuilder builder = new StringBuilder(); + builder.append("

OAuth Approval

"); + builder.append("

Do you authorize \"").append(HtmlUtils.htmlEscape(clientId)); + builder.append("\" to access your protected resources?

"); + builder.append("
"); + builder.append(""); - public SpelView(String template) { - this.template = template; - this.context.addPropertyAccessor(new MapAccessor()); - this.helper = new PropertyPlaceholderHelper("${", "}"); - this.resolver = new PlaceholderResolver() { - public String resolvePlaceholder(String name) { - Expression expression = parser.parseExpression(name); - Object value = expression.getValue(context); - return value==null ? null : value.toString(); - } - }; + String csrfTemplate = null; + CsrfToken csrfToken = (CsrfToken) (model.containsKey("_csrf") ? model.get("_csrf") : request.getAttribute("_csrf")); + if (csrfToken != null) { + csrfTemplate = ""; } - - public String getContentType() { - return "text/html"; + if (csrfTemplate != null) { + builder.append(csrfTemplate); } - public void render(Map model, HttpServletRequest request, HttpServletResponse response) - throws Exception { - Map map = new HashMap(model); - map.put("path", (Object) request.getContextPath()); - context.setRootObject(map); - String result = helper.replacePlaceholders(template, resolver); - response.getWriter().append(result); + String authorizeInputTemplate = ""; + + if (model.containsKey("scopes") || request.getAttribute("scopes") != null) { + builder.append(createScopes(model, request)); + builder.append(authorizeInputTemplate); + } else { + builder.append(authorizeInputTemplate); + builder.append("
"); + builder.append(""); + if (csrfTemplate != null) { + builder.append(csrfTemplate); + } + builder.append("
"); } - } - - private static String APPROVAL = "

OAuth Approval

" - + "

Do you authorize '${authorizationRequest.clientId}' to access your protected resources?

" - + "
" - + "
" - + ""; + builder.append(""); - private static String ERROR = "

OAuth Error

${error.summary}

"; + return builder.toString(); + } -} + private CharSequence createScopes(Map model, HttpServletRequest request) { + StringBuilder builder = new StringBuilder("
    "); + @SuppressWarnings("unchecked") + Map scopes = (Map) (model.containsKey("scopes") ? + model.get("scopes") : request.getAttribute("scopes")); + for (String scope : scopes.keySet()) { + String approved = "true".equals(scopes.get(scope)) ? " checked" : ""; + String denied = !"true".equals(scopes.get(scope)) ? " checked" : ""; + scope = HtmlUtils.htmlEscape(scope); + + builder.append("
  • "); + builder.append(scope).append(": Approve "); + builder.append("Deny
  • "); + } + builder.append("
"); + return builder.toString(); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/WhitelabelErrorEndpoint.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/WhitelabelErrorEndpoint.java new file mode 100644 index 000000000..cdf911b87 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/WhitelabelErrorEndpoint.java @@ -0,0 +1,57 @@ +package org.springframework.security.oauth2.provider.endpoint; + +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.View; +import org.springframework.web.util.HtmlUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.HashMap; +import java.util.Map; + +/** + * Controller for displaying the error page for the authorization server. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + */ +@FrameworkEndpoint +@Deprecated +public class WhitelabelErrorEndpoint { + + private static final String ERROR = "

OAuth Error

%errorSummary%

"; + + @RequestMapping("/oauth/error") + public ModelAndView handleError(HttpServletRequest request) { + Map model = new HashMap(); + Object error = request.getAttribute("error"); + // The error summary may contain malicious user input, + // it needs to be escaped to prevent XSS + String errorSummary; + if (error instanceof OAuth2Exception) { + OAuth2Exception oauthError = (OAuth2Exception) error; + errorSummary = HtmlUtils.htmlEscape(oauthError.getSummary()); + } + else { + errorSummary = "Unknown error"; + } + final String errorContent = ERROR.replace("%errorSummary%", errorSummary); + View errorView = new View() { + @Override + public String getContentType() { + return "text/html"; + } + + @Override + public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { + response.setContentType(getContentType()); + response.getWriter().append(errorContent); + } + }; + return new ModelAndView(errorView, model); + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/AbstractOAuth2SecurityExceptionHandler.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/AbstractOAuth2SecurityExceptionHandler.java index 3e0fe6e5f..26df70549 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/AbstractOAuth2SecurityExceptionHandler.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/AbstractOAuth2SecurityExceptionHandler.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -21,7 +21,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.http.ResponseEntity; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; @@ -29,23 +28,27 @@ /** * Convenient base class containing utility methods and dependency setters for security error handling concerns specific * to OAuth2 resources. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public abstract class AbstractOAuth2SecurityExceptionHandler { /** Logger available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); - private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator(); + private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator(); private OAuth2ExceptionRenderer exceptionRenderer = new DefaultOAuth2ExceptionRenderer(); // This is from Spring MVC. private HandlerExceptionResolver handlerExceptionResolver = new DefaultHandlerExceptionResolver(); - public void setExceptionTranslator(WebResponseExceptionTranslator exceptionTranslator) { + public void setExceptionTranslator(WebResponseExceptionTranslator exceptionTranslator) { this.exceptionTranslator = exceptionTranslator; } @@ -56,7 +59,7 @@ public void setExceptionRenderer(OAuth2ExceptionRenderer exceptionRenderer) { protected final void doHandle(HttpServletRequest request, HttpServletResponse response, Exception authException) throws IOException, ServletException { try { - ResponseEntity result = exceptionTranslator.translate(authException); + ResponseEntity result = exceptionTranslator.translate(authException); result = enhanceResponse(result, authException); exceptionRenderer.handleHttpEntityResponse(result, new ServletWebRequest(request, response)); response.flushBuffer(); @@ -83,11 +86,15 @@ protected final void doHandle(HttpServletRequest request, HttpServletResponse re /** * Allow subclasses to manipulate the response before it is rendered. * + * Note : Only the {@link ResponseEntity} should be enhanced. If the + * response body is to be customized, it should be done at the + * {@link WebResponseExceptionTranslator} level. + * * @param result the response that was generated by the * {@link #setExceptionTranslator(WebResponseExceptionTranslator) exception translator}. * @param authException the authentication exception that is being handled */ - protected ResponseEntity enhanceResponse(ResponseEntity result, + protected ResponseEntity enhanceResponse(ResponseEntity result, Exception authException) { return result; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/DefaultOAuth2ExceptionRenderer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/DefaultOAuth2ExceptionRenderer.java index f5b7b33bb..8c1826a89 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/DefaultOAuth2ExceptionRenderer.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/DefaultOAuth2ExceptionRenderer.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -39,12 +39,17 @@ import org.springframework.web.context.request.ServletWebRequest; /** - * Default implementation of {@link OAuth2ExceptionRenderer} that can render the exceptions as JSON. If the caller only - * accepts a specific media type (like XML) then this implementation will not handle it. - * + * Default implementation of {@link OAuth2ExceptionRenderer} that can render the exceptions using message converters + * (just like regular Spring MVC endpoints). If the caller sends an appropriate Accept header he should get the right + * result as long as an appropriate message converter is provided. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class DefaultOAuth2ExceptionRenderer implements OAuth2ExceptionRenderer { private final Log logger = LogFactory.getLog(DefaultOAuth2ExceptionRenderer.class); diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/DefaultWebResponseExceptionTranslator.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/DefaultWebResponseExceptionTranslator.java index 24ca56287..af725fa8b 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/DefaultWebResponseExceptionTranslator.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/DefaultWebResponseExceptionTranslator.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -27,21 +27,29 @@ import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.web.util.ThrowableAnalyzer; +import org.springframework.web.HttpRequestMethodNotSupportedException; /** + * Default translator that converts exceptions into {@link OAuth2Exception}s. The output matches the OAuth 2.0 + * specification in terms of error response format and HTTP status code. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ -public class DefaultWebResponseExceptionTranslator implements WebResponseExceptionTranslator { +@Deprecated +public class DefaultWebResponseExceptionTranslator implements WebResponseExceptionTranslator { private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer(); + @Override public ResponseEntity translate(Exception e) throws Exception { // Try to extract a SpringSecurityException from the stacktrace Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e); - RuntimeException ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType( - OAuth2Exception.class, causeChain); + Exception ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain); if (ase != null) { return handleOAuth2Exception((OAuth2Exception) ase); @@ -59,7 +67,13 @@ public ResponseEntity translate(Exception e) throws Exception { return handleOAuth2Exception(new ForbiddenException(ase.getMessage(), ase)); } - return handleOAuth2Exception(new ServerErrorException(e.getMessage(), e)); + ase = (HttpRequestMethodNotSupportedException) throwableAnalyzer.getFirstThrowableOfType( + HttpRequestMethodNotSupportedException.class, causeChain); + if (ase instanceof HttpRequestMethodNotSupportedException) { + return handleOAuth2Exception(new MethodNotAllowed(ase.getMessage(), ase)); + } + + return handleOAuth2Exception(new ServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), e)); } @@ -68,6 +82,7 @@ private ResponseEntity handleOAuth2Exception(OAuth2Exception e) int status = e.getHttpErrorCode(); HttpHeaders headers = new HttpHeaders(); headers.set("Cache-Control", "no-store"); + headers.set("Pragma", "no-cache"); if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) { headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary())); } @@ -83,50 +98,79 @@ public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) { this.throwableAnalyzer = throwableAnalyzer; } + @SuppressWarnings("serial") private static class ForbiddenException extends OAuth2Exception { public ForbiddenException(String msg, Throwable t) { super(msg, t); } + @Override public String getOAuth2ErrorCode() { return "access_denied"; } + @Override public int getHttpErrorCode() { return 403; } } + @SuppressWarnings("serial") private static class ServerErrorException extends OAuth2Exception { public ServerErrorException(String msg, Throwable t) { super(msg, t); } + @Override public String getOAuth2ErrorCode() { return "server_error"; } + @Override public int getHttpErrorCode() { return 500; } } + + @SuppressWarnings("serial") private static class UnauthorizedException extends OAuth2Exception { public UnauthorizedException(String msg, Throwable t) { super(msg, t); } + @Override public String getOAuth2ErrorCode() { return "unauthorized"; } + @Override public int getHttpErrorCode() { return 401; } } + + @SuppressWarnings("serial") + private static class MethodNotAllowed extends OAuth2Exception { + + public MethodNotAllowed(String msg, Throwable t) { + super(msg, t); + } + + @Override + public String getOAuth2ErrorCode() { + return "method_not_allowed"; + } + + @Override + public int getHttpErrorCode() { + return 405; + } + + } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/OAuth2AccessDeniedHandler.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/OAuth2AccessDeniedHandler.java index 404c0b957..0d1682f4d 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/OAuth2AccessDeniedHandler.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/OAuth2AccessDeniedHandler.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -25,10 +25,14 @@ * If authorization fails and the caller has asked for a specific content type response, this entry point can send one, * along with a standard 403 status. Add to the Spring Security configuration as an {@link AccessDeniedHandler} in * the usual way. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class OAuth2AccessDeniedHandler extends AbstractOAuth2SecurityExceptionHandler implements AccessDeniedHandler { public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException authException) diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/OAuth2AuthenticationEntryPoint.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/OAuth2AuthenticationEntryPoint.java index 9fecd929a..63c760974 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/OAuth2AuthenticationEntryPoint.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/OAuth2AuthenticationEntryPoint.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -22,7 +22,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.util.StringUtils; @@ -30,10 +29,14 @@ * If authentication fails and the caller has asked for a specific content type response, this entry point can send one, * along with a standard 401 status. Add to the Spring Security configuration as an {@link AuthenticationEntryPoint} in * the usual way. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class OAuth2AuthenticationEntryPoint extends AbstractOAuth2SecurityExceptionHandler implements AuthenticationEntryPoint { @@ -55,7 +58,7 @@ public void commence(HttpServletRequest request, HttpServletResponse response, A } @Override - protected ResponseEntity enhanceResponse(ResponseEntity response, Exception exception) { + protected ResponseEntity enhanceResponse(ResponseEntity response, Exception exception) { HttpHeaders headers = response.getHeaders(); String existing = null; if (headers.containsKey("WWW-Authenticate")) { @@ -70,7 +73,7 @@ protected ResponseEntity enhanceResponse(ResponseEntity(response.getBody(), update, response.getStatusCode()); + return new ResponseEntity(response.getBody(), update, response.getStatusCode()); } private String extractTypePrefix(String header) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/OAuth2ExceptionRenderer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/OAuth2ExceptionRenderer.java index db17479a4..7f1a967e1 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/OAuth2ExceptionRenderer.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/OAuth2ExceptionRenderer.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -19,10 +19,14 @@ /** * Strategy for rendering a {@link OAuth2Exception} in cases where they cannot be rendered by the Spring dispatcher * servlet (i.e. usually in a filter chain). - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface OAuth2ExceptionRenderer { void handleHttpEntityResponse(HttpEntity responseEntity, ServletWebRequest webRequest) throws Exception; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/WebResponseExceptionTranslator.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/WebResponseExceptionTranslator.java index 782a49c99..05030335f 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/WebResponseExceptionTranslator.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/error/WebResponseExceptionTranslator.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,10 +16,19 @@ package org.springframework.security.oauth2.provider.error; import org.springframework.http.ResponseEntity; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; -public interface WebResponseExceptionTranslator { +/** + * Translates exceptions into HTTP Responses. + * + * @param The error model that will be used as the HTTP Response body. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + */ +@Deprecated +public interface WebResponseExceptionTranslator { - ResponseEntity translate(Exception e) throws Exception; + ResponseEntity translate(Exception e) throws Exception; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2ExpressionParser.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2ExpressionParser.java new file mode 100644 index 000000000..f554c7785 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2ExpressionParser.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2011 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.provider.expression; + +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.ParseException; +import org.springframework.expression.ParserContext; +import org.springframework.util.Assert; + +/** + *

+ * A custom {@link ExpressionParser} that automatically wraps SpEL expression with + * {@link OAuth2SecurityExpressionMethods#throwOnError(boolean)}. This makes it simple for users to specify an + * expression and then have it verified (providing errors) after the result of the expression is known. + *

+ *

+ * Note: The implication is that all expressions that are parsed must return a boolean result. This expectation is + * already true since Spring Security expects the result to be a boolean. + *

+ * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Rob Winch + * + */ +@Deprecated +public class OAuth2ExpressionParser implements ExpressionParser { + + private final ExpressionParser delegate; + + public OAuth2ExpressionParser(ExpressionParser delegate) { + Assert.notNull(delegate, "delegate cannot be null"); + this.delegate = delegate; + } + + public Expression parseExpression(String expressionString) throws ParseException { + return delegate.parseExpression(wrapExpression(expressionString)); + } + + public Expression parseExpression(String expressionString, ParserContext context) throws ParseException { + return delegate.parseExpression(wrapExpression(expressionString), context); + } + + private String wrapExpression(String expressionString) { + return "#oauth2.throwOnError(" + expressionString + ")"; + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2ExpressionUtils.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2ExpressionUtils.java index 3a1ff8380..c893053a2 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2ExpressionUtils.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2ExpressionUtils.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -18,18 +18,23 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; /** - * @author Dave Syer + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. * + * @author Dave Syer + * @author Radek Ostrowski + * */ +@Deprecated public abstract class OAuth2ExpressionUtils { public static boolean clientHasAnyRole(Authentication authentication, String... roles) { if (authentication instanceof OAuth2Authentication) { - AuthorizationRequest clientAuthentication = ((OAuth2Authentication) authentication).getAuthorizationRequest(); + OAuth2Request clientAuthentication = ((OAuth2Authentication) authentication).getOAuth2Request(); Collection clientAuthorities = clientAuthentication.getAuthorities(); if (clientAuthorities != null) { Set roleSet = AuthorityUtils.authorityListToSet(clientAuthorities); @@ -74,7 +79,7 @@ public static boolean isOAuthUserAuth(Authentication authentication) { public static boolean hasAnyScope(Authentication authentication, String[] scopes) { if (authentication instanceof OAuth2Authentication) { - AuthorizationRequest clientAuthentication = ((OAuth2Authentication) authentication).getAuthorizationRequest(); + OAuth2Request clientAuthentication = ((OAuth2Authentication) authentication).getOAuth2Request(); Collection assigned = clientAuthentication.getScope(); if (assigned != null) { for (String scope : scopes) { @@ -86,7 +91,22 @@ public static boolean hasAnyScope(Authentication authentication, String[] scopes } return false; + } + public static boolean hasAnyScopeMatching(Authentication authentication, String[] scopesRegex) { + + if (authentication instanceof OAuth2Authentication) { + OAuth2Request clientAuthentication = ((OAuth2Authentication) authentication).getOAuth2Request(); + for (String scope : clientAuthentication.getScope()) { + for (String regex : scopesRegex) { + if (scope.matches(regex)) { + return true; + } + } + } + } + + return false; } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2MethodSecurityExpressionHandler.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2MethodSecurityExpressionHandler.java index a64524ba9..29035fa89 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2MethodSecurityExpressionHandler.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2MethodSecurityExpressionHandler.java @@ -1,38 +1,40 @@ package org.springframework.security.oauth2.provider.expression; import org.aopalliance.intercept.MethodInvocation; +import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.core.Authentication; /** + *

* A security expression handler that can handle default method security expressions plus the set provided by * {@link OAuth2SecurityExpressionMethods} using the variable oauth2 to access the methods. For example, the expression * #oauth2.clientHasRole('ROLE_ADMIN') would invoke {@link OAuth2SecurityExpressionMethods#clientHasRole} + *

+ *

+ * By default the {@link OAuth2ExpressionParser} is used. If this is undesirable one can inject their own + * {@link ExpressionParser} using {@link #setExpressionParser(ExpressionParser)}. + *

+ * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. * * @author Dave Syer * @author Rob Winch + * @see OAuth2ExpressionParser */ +@Deprecated public class OAuth2MethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler { - - private boolean throwExceptionOnInvalidScope = true; - /** - * Flag to determine the behaviour on access denied if the reason is . If set then we throw an - * {@link InvalidScopeException} instead of returning true. This is unconventional for an access decision because it - * vetos the other voters in the chain, but it enables us to pass a message to the caller with information about the - * required scope. - * - * @param throwException the flag to set (default true) - */ - public void setThrowExceptionOnInvalidScope(boolean throwException) { - this.throwExceptionOnInvalidScope = throwException; + public OAuth2MethodSecurityExpressionHandler() { + setExpressionParser(new OAuth2ExpressionParser(getExpressionParser())); } @Override public StandardEvaluationContext createEvaluationContextInternal(Authentication authentication, MethodInvocation mi) { StandardEvaluationContext ec = super.createEvaluationContextInternal(authentication, mi); - ec.setVariable("oauth2", new OAuth2SecurityExpressionMethods(authentication, throwExceptionOnInvalidScope)); + ec.setVariable("oauth2", new OAuth2SecurityExpressionMethods(authentication)); return ec; } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2SecurityExpressionMethods.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2SecurityExpressionMethods.java index 7479e9b08..4ec9e28ff 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2SecurityExpressionMethods.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2SecurityExpressionMethods.java @@ -4,14 +4,13 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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.provider.expression; import java.util.Arrays; @@ -25,45 +24,63 @@ /** * A convenience object for security expressions in OAuth2 protected resources, providing public methods that act on the * current authentication. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * @author Rob Winch + * @author Radek Ostrowski * */ +@Deprecated public class OAuth2SecurityExpressionMethods { private final Authentication authentication; private Set missingScopes = new LinkedHashSet(); - private boolean throwExceptionOnInvalidScope; - - public OAuth2SecurityExpressionMethods(Authentication authentication, boolean throwExceptionOnInvalidScope) { + public OAuth2SecurityExpressionMethods(Authentication authentication) { this.authentication = authentication; - this.throwExceptionOnInvalidScope = throwExceptionOnInvalidScope; } /** - * Check if any scope decisions have been denied in the current context and throw an exception if so. Example usage: + * Check if any scope decisions have been denied in the current context and throw an exception if so. This method + * automatically wraps any expressions when using {@link OAuth2MethodSecurityExpressionHandler} or + * {@link OAuth2WebSecurityExpressionHandler}. + * + * OAuth2Example usage: * *

-	 * access = "#oauth2.sufficientScope(#oauth2.hasScope('read') or (#oauth2.hasScope('other') and hasRole('ROLE_USER')))"
+	 * access = "#oauth2.hasScope('read') or (#oauth2.hasScope('other') and hasRole('ROLE_USER'))"
 	 * 
* + * Will automatically be wrapped to ensure that explicit errors are propagated rather than a generic error when + * returning false: + * + *
+	 * access = "#oauth2.throwOnError(#oauth2.hasScope('read') or (#oauth2.hasScope('other') and hasRole('ROLE_USER'))"
+	 * 
+ * + * N.B. normally this method will be automatically wrapped around all your access expressions. You could use it + * explicitly to get more control, or if you have registered your own ExpressionParser you might need + * it. + * * @param decision the existing access decision * @return true if the OAuth2 token has one of these scopes * @throws InsufficientScopeException if the scope is invalid and we the flag is set to throw the exception */ - public boolean sufficientScope(boolean decision) { + public boolean throwOnError(boolean decision) { if (!decision && !missingScopes.isEmpty()) { - throw new InsufficientScopeException("Insufficient scope for this resource", missingScopes); + Throwable failure = new InsufficientScopeException("Insufficient scope for this resource", missingScopes); + throw new AccessDeniedException(failure.getMessage(), failure); } return decision; } /** * Check if the OAuth2 client (not the user) has the role specified. To check the user's roles see - * {@link #hasRole(String)}. + * {@link #clientHasAnyRole(String...)}. * * @param role the role to check * @return true if the OAuth2 client has this role @@ -74,7 +91,7 @@ public boolean clientHasRole(String role) { /** * Check if the OAuth2 client (not the user) has one of the roles specified. To check the user's roles see - * {@link #hasAnyRole(String)}. + * {@link OAuth2ExpressionUtils#clientHasAnyRole(Authentication, String...)}. * * @param roles the roles to check * @return true if the OAuth2 client has one of these roles @@ -84,7 +101,7 @@ public boolean clientHasAnyRole(String... roles) { } /** - * Check if the current OAuth2 authentication has one of the scopes specified. + * Check if the current OAuth2 authentication has the scope specified. * * @param scope the scope to check * @return true if the OAuth2 authentication has the required scope @@ -96,16 +113,48 @@ public boolean hasScope(String scope) { /** * Check if the current OAuth2 authentication has one of the scopes specified. * - * @param roles the scopes to check + * @param scopes the scopes to check * @return true if the OAuth2 token has one of these scopes * @throws AccessDeniedException if the scope is invalid and we the flag is set to throw the exception */ public boolean hasAnyScope(String... scopes) { boolean result = OAuth2ExpressionUtils.hasAnyScope(authentication, scopes); - if (!result && throwExceptionOnInvalidScope) { + if (!result) { missingScopes.addAll(Arrays.asList(scopes)); - Throwable failure = new InsufficientScopeException("Insufficient scope for this resource", missingScopes); - throw new AccessDeniedException(failure.getMessage(), failure); + } + return result; + } + + /** + * Check if the current OAuth2 authentication has one of the scopes matching a specified regex expression. + * + *
+	 * access = "#oauth2.hasScopeMatching('.*_admin:manage_scopes')))"
+	 * 
+ * + * @param scopeRegex the scope regex to match + * @return true if the OAuth2 authentication has the required scope + */ + public boolean hasScopeMatching(String scopeRegex) { + return hasAnyScopeMatching(scopeRegex); + } + + /** + * Check if the current OAuth2 authentication has one of the scopes matching a specified regex expression. + * + *
+	 * access = "#oauth2.hasAnyScopeMatching('admin:manage_scopes','.*_admin:manage_scopes','.*_admin:read_scopes')))"
+	 * 
+ * + * @param scopesRegex the scopes regex to match + * @return true if the OAuth2 token has one of these scopes + * @throws AccessDeniedException if the scope is invalid and we the flag is set to throw the exception + */ + public boolean hasAnyScopeMatching(String... scopesRegex) { + + boolean result = OAuth2ExpressionUtils.hasAnyScopeMatching(authentication, scopesRegex); + if (!result) { + missingScopes.addAll(Arrays.asList(scopesRegex)); } return result; } @@ -119,6 +168,15 @@ public boolean denyOAuthClient() { return !OAuth2ExpressionUtils.isOAuth(authentication); } + /** + * Permit access to oauth requests, so used for example to only allow machine clients to access a resource. + * + * @return true if the current authentication is not an OAuth2 type + */ + public boolean isOAuth() { + return OAuth2ExpressionUtils.isOAuth(authentication); + } + /** * Check if the current authentication is acting on behalf of an authenticated user. * @@ -136,13 +194,4 @@ public boolean isUser() { public boolean isClient() { return OAuth2ExpressionUtils.isOAuthClientAuth(authentication); } - - /** - * A flag to indicate that an exception should be thrown if a scope decision is negative. - * - * @param throwExceptionOnInvalidScope flag value (default true) - */ - public void setThrowExceptionOnInvalidScope(boolean throwExceptionOnInvalidScope) { - this.throwExceptionOnInvalidScope = throwExceptionOnInvalidScope; - } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2WebSecurityExpressionHandler.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2WebSecurityExpressionHandler.java index 7b5bd0c70..40881dfcc 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2WebSecurityExpressionHandler.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/expression/OAuth2WebSecurityExpressionHandler.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -12,40 +12,42 @@ */ package org.springframework.security.oauth2.provider.expression; +import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.security.core.Authentication; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler; /** + *

* A security expression handler that can handle default web security expressions plus the set provided by * {@link OAuth2SecurityExpressionMethods} using the variable oauth2 to access the methods. For example, the expression * #oauth2.clientHasRole('ROLE_ADMIN') would invoke {@link OAuth2SecurityExpressionMethods#clientHasRole}. - * + *

+ *

+ * By default the {@link OAuth2ExpressionParser} is used. If this is undesirable one can inject their own + * {@link ExpressionParser} using {@link #setExpressionParser(ExpressionParser)}. + *

+ * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * @author Rob Winch * + * @see OAuth2ExpressionParser */ +@Deprecated public class OAuth2WebSecurityExpressionHandler extends DefaultWebSecurityExpressionHandler { - private boolean throwExceptionOnInvalidScope = true; - - /** - * Flag to determine the behaviour on access denied if the reason is . If set then we throw an - * {@link InvalidScopeException} instead of returning true. This is unconventional for an access decision because it - * vetos the other voters in the chain, but it enables us to pass a message to the caller with information about the - * required scope. - * - * @param throwException the flag to set (default true) - */ - public void setThrowExceptionOnInvalidScope(boolean throwException) { - this.throwExceptionOnInvalidScope = throwException; + public OAuth2WebSecurityExpressionHandler() { + setExpressionParser(new OAuth2ExpressionParser(getExpressionParser())); } - + @Override protected StandardEvaluationContext createEvaluationContextInternal(Authentication authentication, FilterInvocation invocation) { StandardEvaluationContext ec = super.createEvaluationContextInternal(authentication, invocation); - ec.setVariable("oauth2", new OAuth2SecurityExpressionMethods(authentication, throwExceptionOnInvalidScope)); + ec.setVariable("oauth2", new OAuth2SecurityExpressionMethods(authentication)); return ec; } } \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/implicit/ImplicitGrantService.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/implicit/ImplicitGrantService.java new file mode 100644 index 000000000..ea1797143 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/implicit/ImplicitGrantService.java @@ -0,0 +1,36 @@ +package org.springframework.security.oauth2.provider.implicit; + +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.TokenRequest; + +/** + * Service to associate & store an incoming AuthorizationRequest with the TokenRequest that is passed + * to the ImplicitTokenGranter during the Implicit flow. This mimics the AuthorizationCodeServices + * functionality from the Authorization Code flow, allowing the ImplicitTokenGranter to reference the original + * AuthorizationRequest, while still allowing the ImplicitTokenGranter to adhere to the TokenGranter interface. + * + * @author Amanda Anganes + * + * @deprecated with no replacement (it shouldn't be necessary to use this strategy since 2.0.2) + * + */ +@Deprecated +public interface ImplicitGrantService { + + /** + * Save an association between an OAuth2Request and a TokenRequest. + * + * @param originalRequest + * @param tokenRequest + */ + public void store(OAuth2Request originalRequest, TokenRequest tokenRequest); + + /** + * Look up and return the OAuth2Request associated with the given TokenRequest. + * + * @param tokenRequest + * @return + */ + public OAuth2Request remove(TokenRequest tokenRequest); + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/implicit/ImplicitTokenGranter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/implicit/ImplicitTokenGranter.java index 043d18f3b..975ed02b8 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/implicit/ImplicitTokenGranter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/implicit/ImplicitTokenGranter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -20,34 +20,54 @@ import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; +import org.springframework.util.Assert; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class ImplicitTokenGranter extends AbstractTokenGranter { private static final String GRANT_TYPE = "implicit"; - public ImplicitTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService) { - super(tokenServices, clientDetailsService, GRANT_TYPE); + public ImplicitTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { + this(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); + } + + protected ImplicitTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, + OAuth2RequestFactory requestFactory, String grantType) { + super(tokenServices, clientDetailsService, requestFactory, grantType); } @Override - protected OAuth2Authentication getOAuth2Authentication(AuthorizationRequest clientToken) { + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest clientToken) { Authentication userAuth = SecurityContextHolder.getContext().getAuthentication(); if (userAuth==null || !userAuth.isAuthenticated()) { throw new InsufficientAuthenticationException("There is no currently logged in user"); } + Assert.state(clientToken instanceof ImplicitTokenRequest, "An ImplicitTokenRequest is required here. Caller needs to wrap the TokenRequest."); + + OAuth2Request requestForStorage = ((ImplicitTokenRequest)clientToken).getOAuth2Request(); + + return new OAuth2Authentication(requestForStorage, userAuth); - return new OAuth2Authentication(clientToken, userAuth); - + } + + @SuppressWarnings("deprecation") + public void setImplicitGrantService(ImplicitGrantService service) { } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/implicit/ImplicitTokenRequest.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/implicit/ImplicitTokenRequest.java new file mode 100644 index 000000000..2da7b57fc --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/implicit/ImplicitTokenRequest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013-2014 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.provider.implicit; + +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.TokenRequest; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + * @since 2.0.2 + * + */ +@SuppressWarnings("serial") +@Deprecated +public class ImplicitTokenRequest extends TokenRequest { + + private OAuth2Request oauth2Request; + + public ImplicitTokenRequest(TokenRequest tokenRequest, OAuth2Request oauth2Request) { + super(tokenRequest.getRequestParameters(), tokenRequest.getClientId(), tokenRequest.getScope(), tokenRequest.getGrantType()); + this.oauth2Request = oauth2Request; + } + + public OAuth2Request getOAuth2Request() { + return oauth2Request; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/implicit/InMemoryImplicitGrantService.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/implicit/InMemoryImplicitGrantService.java new file mode 100644 index 000000000..d374d144a --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/implicit/InMemoryImplicitGrantService.java @@ -0,0 +1,32 @@ +package org.springframework.security.oauth2.provider.implicit; + +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.TokenRequest; + +/** + * In-memory implementation of the ImplicitGrantService. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Amanda Anganes + * + */ +@SuppressWarnings("deprecation") +@Deprecated +public class InMemoryImplicitGrantService implements ImplicitGrantService { + + protected final ConcurrentHashMap requestStore = new ConcurrentHashMap(); + + public void store(OAuth2Request originalRequest, TokenRequest tokenRequest) { + this.requestStore.put(tokenRequest, originalRequest); + } + + public OAuth2Request remove(TokenRequest tokenRequest) { + OAuth2Request request = this.requestStore.remove(tokenRequest); + return request; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/password/ResourceOwnerPasswordTokenGranter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/password/ResourceOwnerPasswordTokenGranter.java index 616a4ec5e..d4b2cfedc 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/password/ResourceOwnerPasswordTokenGranter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/password/ResourceOwnerPasswordTokenGranter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,24 +16,34 @@ package org.springframework.security.oauth2.provider.password; - +import java.util.LinkedHashMap; import java.util.Map; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.authentication.AccountStatusException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; -import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class ResourceOwnerPasswordTokenGranter extends AbstractTokenGranter { private static final String GRANT_TYPE = "password"; @@ -41,32 +51,47 @@ public class ResourceOwnerPasswordTokenGranter extends AbstractTokenGranter { private final AuthenticationManager authenticationManager; public ResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager, - AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService) { - super(tokenServices, clientDetailsService, GRANT_TYPE); + AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { + this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); + } + + protected ResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, + ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) { + super(tokenServices, clientDetailsService, requestFactory, grantType); this.authenticationManager = authenticationManager; } @Override - protected OAuth2Authentication getOAuth2Authentication(AuthorizationRequest clientToken) { + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { - Map parameters = clientToken.getAuthorizationParameters(); + Map parameters = new LinkedHashMap(tokenRequest.getRequestParameters()); String username = parameters.get("username"); String password = parameters.get("password"); + // Protect from downstream leaks of password + parameters.remove("password"); Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password); + ((AbstractAuthenticationToken) userAuth).setDetails(parameters); try { userAuth = authenticationManager.authenticate(userAuth); } + catch (AccountStatusException ase) { + //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31) + throw new InvalidGrantException(ase.getMessage()); + } catch (BadCredentialsException e) { - // If the username/password are wrong the spec says we should send 400/bad grant + // If the username/password are wrong the spec says we should send 400/invalid grant throw new InvalidGrantException(e.getMessage()); } + catch (UsernameNotFoundException e) { + // If the user is not found, report a generic error message + throw new InvalidGrantException("username not found"); + } if (userAuth == null || !userAuth.isAuthenticated()) { - throw new InvalidGrantException("Could not authenticate user: " + username); + throw new InvalidGrantException("Could not authenticate user"); } - - return new OAuth2Authentication(clientToken, userAuth); - + + OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); + return new OAuth2Authentication(storedOAuth2Request, userAuth); } - } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/refresh/RefreshTokenGranter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/refresh/RefreshTokenGranter.java index 38ae449da..242b09c32 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/refresh/RefreshTokenGranter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/refresh/RefreshTokenGranter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,28 +16,50 @@ package org.springframework.security.oauth2.provider.refresh; +import org.springframework.security.authentication.AccountStatusException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class RefreshTokenGranter extends AbstractTokenGranter { private static final String GRANT_TYPE = "refresh_token"; - public RefreshTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService) { - super(tokenServices, clientDetailsService, GRANT_TYPE); + public RefreshTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { + this(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); + } + + protected RefreshTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, + OAuth2RequestFactory requestFactory, String grantType) { + super(tokenServices, clientDetailsService, requestFactory, grantType); } @Override - protected OAuth2AccessToken getAccessToken(AuthorizationRequest authorizationRequest) { - String refreshToken = authorizationRequest.getAuthorizationParameters().get("refresh_token"); - return getTokenServices().refreshAccessToken(refreshToken, authorizationRequest); + protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) { + String refreshToken = tokenRequest.getRequestParameters().get("refresh_token"); + try { + return getTokenServices().refreshAccessToken(refreshToken, tokenRequest); + } + catch (AccountStatusException ase) { + //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31) + throw new InvalidGrantException(ase.getMessage()); + } catch (UsernameNotFoundException e) { + // If the user is not found, report a generic error message + throw new InvalidGrantException("user not found"); + } } - } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/request/DefaultOAuth2RequestFactory.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/request/DefaultOAuth2RequestFactory.java new file mode 100644 index 000000000..c039aadc0 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/request/DefaultOAuth2RequestFactory.java @@ -0,0 +1,161 @@ +/* + * Copyright 2006-2011 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.provider.request; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.common.exceptions.InvalidClientException; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.DefaultSecurityContextAccessor; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.SecurityContextAccessor; +import org.springframework.security.oauth2.provider.TokenRequest; + +/** + * Default implementation of {@link OAuth2RequestFactory} which initializes fields from the parameters map, validates + * grant types and scopes, and fills in scopes with the default values from the client if they are missing. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * @author Amanda Anganes + * + */ +@Deprecated +public class DefaultOAuth2RequestFactory implements OAuth2RequestFactory { + + private final ClientDetailsService clientDetailsService; + + private SecurityContextAccessor securityContextAccessor = new DefaultSecurityContextAccessor(); + + private boolean checkUserScopes = false; + + public DefaultOAuth2RequestFactory(ClientDetailsService clientDetailsService) { + this.clientDetailsService = clientDetailsService; + } + + /** + * @param securityContextAccessor the security context accessor to set + */ + public void setSecurityContextAccessor(SecurityContextAccessor securityContextAccessor) { + this.securityContextAccessor = securityContextAccessor; + } + + /** + * Flag to indicate that scopes should be interpreted as valid authorities. No scopes will be granted to a user + * unless they are permitted as a granted authority to that user. + * + * @param checkUserScopes the checkUserScopes to set (default false) + */ + public void setCheckUserScopes(boolean checkUserScopes) { + this.checkUserScopes = checkUserScopes; + } + + public AuthorizationRequest createAuthorizationRequest(Map authorizationParameters) { + + String clientId = authorizationParameters.get(OAuth2Utils.CLIENT_ID); + String state = authorizationParameters.get(OAuth2Utils.STATE); + String redirectUri = authorizationParameters.get(OAuth2Utils.REDIRECT_URI); + Set responseTypes = OAuth2Utils.parseParameterList(authorizationParameters + .get(OAuth2Utils.RESPONSE_TYPE)); + + Set scopes = extractScopes(authorizationParameters, clientId); + + AuthorizationRequest request = new AuthorizationRequest(authorizationParameters, + Collections. emptyMap(), clientId, scopes, null, null, false, state, redirectUri, + responseTypes); + + ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId); + request.setResourceIdsAndAuthoritiesFromClientDetails(clientDetails); + + return request; + + } + + public OAuth2Request createOAuth2Request(AuthorizationRequest request) { + return request.createOAuth2Request(); + } + + public TokenRequest createTokenRequest(Map requestParameters, ClientDetails authenticatedClient) { + + String clientId = requestParameters.get(OAuth2Utils.CLIENT_ID); + if (clientId == null) { + // if the clientId wasn't passed in in the map, we add pull it from the authenticated client object + clientId = authenticatedClient.getClientId(); + } + else { + // otherwise, make sure that they match + if (!clientId.equals(authenticatedClient.getClientId())) { + throw new InvalidClientException("Given client ID does not match authenticated client"); + } + } + String grantType = requestParameters.get(OAuth2Utils.GRANT_TYPE); + + Set scopes = extractScopes(requestParameters, clientId); + TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scopes, grantType); + + return tokenRequest; + } + + public TokenRequest createTokenRequest(AuthorizationRequest authorizationRequest, String grantType) { + TokenRequest tokenRequest = new TokenRequest(authorizationRequest.getRequestParameters(), + authorizationRequest.getClientId(), authorizationRequest.getScope(), grantType); + return tokenRequest; + } + + public OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest) { + return tokenRequest.createOAuth2Request(client); + } + + private Set extractScopes(Map requestParameters, String clientId) { + Set scopes = OAuth2Utils.parseParameterList(requestParameters.get(OAuth2Utils.SCOPE)); + ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId); + + if ((scopes == null || scopes.isEmpty())) { + // If no scopes are specified in the incoming data, use the default values registered with the client + // (the spec allows us to choose between this option and rejecting the request completely, so we'll take the + // least obnoxious choice as a default). + scopes = clientDetails.getScope(); + } + + if (checkUserScopes) { + scopes = checkUserScopes(scopes, clientDetails); + } + return scopes; + } + + private Set checkUserScopes(Set scopes, ClientDetails clientDetails) { + if (!securityContextAccessor.isUser()) { + return scopes; + } + Set result = new LinkedHashSet(); + Set authorities = AuthorityUtils.authorityListToSet(securityContextAccessor.getAuthorities()); + for (String scope : scopes) { + if (authorities.contains(scope) || authorities.contains(scope.toUpperCase()) + || authorities.contains("ROLE_" + scope.toUpperCase())) { + result.add(scope); + } + } + return result; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/request/DefaultOAuth2RequestValidator.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/request/DefaultOAuth2RequestValidator.java new file mode 100644 index 000000000..ce24ca862 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/request/DefaultOAuth2RequestValidator.java @@ -0,0 +1,46 @@ +package org.springframework.security.oauth2.provider.request; + +import java.util.Set; + +import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.OAuth2RequestValidator; +import org.springframework.security.oauth2.provider.TokenRequest; + +/** + * Default implementation of {@link OAuth2RequestValidator}. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Amanda Anganes + * + */ +@Deprecated +public class DefaultOAuth2RequestValidator implements OAuth2RequestValidator { + + public void validateScope(AuthorizationRequest authorizationRequest, ClientDetails client) throws InvalidScopeException { + validateScope(authorizationRequest.getScope(), client.getScope()); + } + + public void validateScope(TokenRequest tokenRequest, ClientDetails client) throws InvalidScopeException { + validateScope(tokenRequest.getScope(), client.getScope()); + } + + private void validateScope(Set requestScopes, Set clientScopes) { + + if (clientScopes != null && !clientScopes.isEmpty()) { + for (String scope : requestScopes) { + if (!clientScopes.contains(scope)) { + throw new InvalidScopeException("Invalid scope", clientScopes); + } + } + } + + if (requestScopes.isEmpty()) { + throw new InvalidScopeException("Empty scope (either the client or the user is not allowed the requested scopes)"); + } + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AbstractTokenGranter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AbstractTokenGranter.java index 8b800fac5..94a931c18 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AbstractTokenGranter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AbstractTokenGranter.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -17,18 +17,23 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; -import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; import org.springframework.security.oauth2.provider.TokenGranter; +import org.springframework.security.oauth2.provider.TokenRequest; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public abstract class AbstractTokenGranter implements TokenGranter { protected final Log logger = LogFactory.getLog(getClass()); @@ -37,51 +42,59 @@ public abstract class AbstractTokenGranter implements TokenGranter { private final ClientDetailsService clientDetailsService; + private final OAuth2RequestFactory requestFactory; + private final String grantType; protected AbstractTokenGranter(AuthorizationServerTokenServices tokenServices, - ClientDetailsService clientDetailsService, String grantType) { + ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) { this.clientDetailsService = clientDetailsService; this.grantType = grantType; this.tokenServices = tokenServices; + this.requestFactory = requestFactory; } - public OAuth2AccessToken grant(String grantType, AuthorizationRequest authorizationRequest) { + public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { if (!this.grantType.equals(grantType)) { return null; } - String clientId = authorizationRequest.getClientId(); + String clientId = tokenRequest.getClientId(); ClientDetails client = clientDetailsService.loadClientByClientId(clientId); validateGrantType(grantType, client); - - logger.debug("Getting access token for: " + clientId); - return getAccessToken(authorizationRequest); + + if (logger.isDebugEnabled()) { + logger.debug("Getting access token for clientId"); + } + + return getAccessToken(client, tokenRequest); } - protected OAuth2AccessToken getAccessToken(AuthorizationRequest authorizationRequest) { - DefaultAuthorizationRequest outgoingRequest = new DefaultAuthorizationRequest(authorizationRequest); - outgoingRequest.setApproved(true); - // FIXME: do we need to explicitly set approved flag here? - return tokenServices.createAccessToken(getOAuth2Authentication(outgoingRequest)); + protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) { + return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest)); } - protected OAuth2Authentication getOAuth2Authentication(AuthorizationRequest authorizationRequest) { - return new OAuth2Authentication(authorizationRequest, null); + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { + OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest); + return new OAuth2Authentication(storedOAuth2Request, null); } protected void validateGrantType(String grantType, ClientDetails clientDetails) { Collection authorizedGrantTypes = clientDetails.getAuthorizedGrantTypes(); if (authorizedGrantTypes != null && !authorizedGrantTypes.isEmpty() && !authorizedGrantTypes.contains(grantType)) { - throw new InvalidGrantException("Unauthorized grant type: " + grantType); + throw new InvalidClientException("Unauthorized grant type"); } } protected AuthorizationServerTokenServices getTokenServices() { return tokenServices; } + + protected OAuth2RequestFactory getRequestFactory() { + return requestFactory; + } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AccessTokenConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AccessTokenConverter.java new file mode 100644 index 000000000..e3fc2a430 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AccessTokenConverter.java @@ -0,0 +1,76 @@ +/* + * Cloud Foundry 2012.02.03 Beta + * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + */ +package org.springframework.security.oauth2.provider.token; + +import java.util.Map; + +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.OAuth2Authentication; + +/** + * Converter interface for token service implementations that store authentication data inside the token. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public interface AccessTokenConverter { + + final String AUD = "aud"; + + final String CLIENT_ID = "client_id"; + + final String EXP = "exp"; + + final String JTI = "jti"; + + final String GRANT_TYPE = "grant_type"; + + final String ATI = "ati"; + + final String SCOPE = OAuth2AccessToken.SCOPE; + + final String AUTHORITIES = "authorities"; + + /** + * @param token an access token + * @param authentication the current OAuth authentication + * + * @return a map representation of the token suitable for a JSON response + * + */ + Map convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication); + + /** + * Recover an access token from the converted value. Half the inverse of + * {@link #convertAccessToken(OAuth2AccessToken, OAuth2Authentication)}. + * + * @param value the token value + * @param map information decoded from an access token + * @return an access token + */ + OAuth2AccessToken extractAccessToken(String value, Map map); + + /** + * Recover an {@link OAuth2Authentication} from the converted access token. Half the inverse of + * {@link #convertAccessToken(OAuth2AccessToken, OAuth2Authentication)}. + * + * @param map information decoded from an access token + * @return an authentication representing the client and user (if there is one) + */ + OAuth2Authentication extractAuthentication(Map map); + +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AuthenticationKeyGenerator.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AuthenticationKeyGenerator.java index 8e90b4c51..4b9f515e2 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AuthenticationKeyGenerator.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AuthenticationKeyGenerator.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -16,10 +16,14 @@ /** * Strategy interface for extracting a unique key from an {@link OAuth2Authentication}. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface AuthenticationKeyGenerator { /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AuthorizationServerTokenServices.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AuthorizationServerTokenServices.java index 6d1e0d08f..7fd9bc359 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AuthorizationServerTokenServices.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/AuthorizationServerTokenServices.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -18,18 +18,21 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.TokenRequest; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Dave Syer */ +@Deprecated public interface AuthorizationServerTokenServices { /** * Create an access token associated with the specified credentials. - * * @param authentication The credentials associated with the access token. * @return The access token. * @throws AuthenticationException If the credentials are inadequate. @@ -42,11 +45,11 @@ public interface AuthorizationServerTokenServices { * (if provided). * * @param refreshToken The details about the refresh token. - * @param request The incoming authorization request. + * @param tokenRequest The incoming token request. * @return The (new) access token. * @throws AuthenticationException If the refresh token is invalid or expired. */ - OAuth2AccessToken refreshAccessToken(String refreshToken, AuthorizationRequest request) + OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest) throws AuthenticationException; /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/ConsumerTokenServices.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/ConsumerTokenServices.java index e8ea0b855..f9ad8d15c 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/ConsumerTokenServices.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/ConsumerTokenServices.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -12,22 +12,17 @@ */ package org.springframework.security.oauth2.provider.token; -import java.util.Collection; - -import org.springframework.security.oauth2.common.OAuth2AccessToken; /** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface ConsumerTokenServices { - Collection findTokensByUserName(String userName); - - Collection findTokensByClientId(String clientId); - boolean revokeToken(String tokenValue); - String getClientId(String tokenValue); - } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/DefaultAccessTokenConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/DefaultAccessTokenConverter.java new file mode 100644 index 000000000..dc97af8af --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/DefaultAccessTokenConverter.java @@ -0,0 +1,193 @@ +/* + * Cloud Foundry 2012.02.03 Beta + * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + */ +package org.springframework.security.oauth2.provider.token; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; + +/** + * Default implementation of {@link AccessTokenConverter}. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * @author Vedran Pavic + */ +@Deprecated +public class DefaultAccessTokenConverter implements AccessTokenConverter { + + private UserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter(); + + private boolean includeGrantType; + + private String scopeAttribute = SCOPE; + + private String clientIdAttribute = CLIENT_ID; + + /** + * Converter for the part of the data in the token representing a user. + * + * @param userTokenConverter the userTokenConverter to set + */ + public void setUserTokenConverter(UserAuthenticationConverter userTokenConverter) { + this.userTokenConverter = userTokenConverter; + } + + /** + * Flag to indicate the the grant type should be included in the converted token. + * + * @param includeGrantType the flag value (default false) + */ + public void setIncludeGrantType(boolean includeGrantType) { + this.includeGrantType = includeGrantType; + } + + /** + * Set scope attribute name to be used in the converted token. Defaults to + * {@link AccessTokenConverter#SCOPE}. + * + * @param scopeAttribute the scope attribute name to use + */ + public void setScopeAttribute(String scopeAttribute) { + this.scopeAttribute = scopeAttribute; + } + + /** + * Set client id attribute name to be used in the converted token. Defaults to + * {@link AccessTokenConverter#CLIENT_ID}. + * + * @param clientIdAttribute the client id attribute name to use + */ + public void setClientIdAttribute(String clientIdAttribute) { + this.clientIdAttribute = clientIdAttribute; + } + + public Map convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { + Map response = new HashMap(); + OAuth2Request clientToken = authentication.getOAuth2Request(); + + if (!authentication.isClientOnly()) { + response.putAll(userTokenConverter.convertUserAuthentication(authentication.getUserAuthentication())); + } else { + if (clientToken.getAuthorities()!=null && !clientToken.getAuthorities().isEmpty()) { + response.put(UserAuthenticationConverter.AUTHORITIES, + AuthorityUtils.authorityListToSet(clientToken.getAuthorities())); + } + } + + if (token.getScope()!=null) { + response.put(scopeAttribute, token.getScope()); + } + if (token.getAdditionalInformation().containsKey(JTI)) { + response.put(JTI, token.getAdditionalInformation().get(JTI)); + } + + if (token.getExpiration() != null) { + response.put(EXP, token.getExpiration().getTime() / 1000); + } + + if (includeGrantType && authentication.getOAuth2Request().getGrantType()!=null) { + response.put(GRANT_TYPE, authentication.getOAuth2Request().getGrantType()); + } + + response.putAll(token.getAdditionalInformation()); + + response.put(clientIdAttribute, clientToken.getClientId()); + if (clientToken.getResourceIds() != null && !clientToken.getResourceIds().isEmpty()) { + response.put(AUD, clientToken.getResourceIds()); + } + return response; + } + + public OAuth2AccessToken extractAccessToken(String value, Map map) { + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(value); + Map info = new HashMap(map); + info.remove(EXP); + info.remove(AUD); + info.remove(clientIdAttribute); + info.remove(scopeAttribute); + if (map.containsKey(EXP)) { + token.setExpiration(new Date((Long) map.get(EXP) * 1000L)); + } + if (map.containsKey(JTI)) { + info.put(JTI, map.get(JTI)); + } + token.setScope(extractScope(map)); + token.setAdditionalInformation(info); + return token; + } + + public OAuth2Authentication extractAuthentication(Map map) { + Map parameters = new HashMap(); + Set scope = extractScope(map); + Authentication user = userTokenConverter.extractAuthentication(map); + String clientId = (String) map.get(clientIdAttribute); + parameters.put(clientIdAttribute, clientId); + if (includeGrantType && map.containsKey(GRANT_TYPE)) { + parameters.put(GRANT_TYPE, (String) map.get(GRANT_TYPE)); + } + Set resourceIds = new LinkedHashSet(map.containsKey(AUD) ? getAudience(map) + : Collections.emptySet()); + + Collection authorities = null; + if (user==null && map.containsKey(AUTHORITIES)) { + @SuppressWarnings("unchecked") + String[] roles = ((Collection)map.get(AUTHORITIES)).toArray(new String[0]); + authorities = AuthorityUtils.createAuthorityList(roles); + } + OAuth2Request request = new OAuth2Request(parameters, clientId, authorities, true, scope, resourceIds, null, null, + null); + return new OAuth2Authentication(request, user); + } + + private Collection getAudience(Map map) { + Object auds = map.get(AUD); + if (auds instanceof Collection) { + @SuppressWarnings("unchecked") + Collection result = (Collection) auds; + return result; + } + return Collections.singleton((String)auds); + } + + private Set extractScope(Map map) { + Set scope = Collections.emptySet(); + if (map.containsKey(scopeAttribute)) { + Object scopeObj = map.get(scopeAttribute); + if (String.class.isInstance(scopeObj)) { + scope = new LinkedHashSet(Arrays.asList(String.class.cast(scopeObj).split(" "))); + } else if (Collection.class.isAssignableFrom(scopeObj.getClass())) { + @SuppressWarnings("unchecked") + Collection scopeColl = (Collection) scopeObj; + scope = new LinkedHashSet(scopeColl); // Preserve ordering + } + } + return scope; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/DefaultAuthenticationKeyGenerator.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/DefaultAuthenticationKeyGenerator.java index 470458727..ae79cf2cf 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/DefaultAuthenticationKeyGenerator.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/DefaultAuthenticationKeyGenerator.java @@ -1,10 +1,10 @@ /* - * Copyright 2006-2011 the original author or authors. + * Copyright 2006-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -12,24 +12,29 @@ */ package org.springframework.security.oauth2.provider.token; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; + import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.LinkedHashMap; import java.util.Map; - -import org.springframework.security.oauth2.common.util.OAuth2Utils; -import org.springframework.security.oauth2.provider.AuthorizationRequest; -import org.springframework.security.oauth2.provider.OAuth2Authentication; +import java.util.TreeSet; /** - * Basic key generator taking into account the client id, scope, reource ids and username (principal name) if they + * Basic key generator taking into account the client id, scope, resource ids and username (principal name) if they * exist. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer - * + * */ +@Deprecated public class DefaultAuthenticationKeyGenerator implements AuthenticationKeyGenerator { private static final String CLIENT_ID = "client_id"; @@ -40,29 +45,27 @@ public class DefaultAuthenticationKeyGenerator implements AuthenticationKeyGener public String extractKey(OAuth2Authentication authentication) { Map values = new LinkedHashMap(); - AuthorizationRequest authorizationRequest = authentication.getAuthorizationRequest(); + OAuth2Request authorizationRequest = authentication.getOAuth2Request(); if (!authentication.isClientOnly()) { values.put(USERNAME, authentication.getName()); } values.put(CLIENT_ID, authorizationRequest.getClientId()); if (authorizationRequest.getScope() != null) { - values.put(SCOPE, OAuth2Utils.formatParameterList(authorizationRequest.getScope())); + values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet(authorizationRequest.getScope()))); } + return generateKey(values); + } + + protected String generateKey(Map values) { MessageDigest digest; try { digest = MessageDigest.getInstance("MD5"); - } - catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK)."); - } - - try { byte[] bytes = digest.digest(values.toString().getBytes("UTF-8")); return String.format("%032x", new BigInteger(1, bytes)); - } - catch (UnsupportedEncodingException e) { - throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK)."); + } catch (NoSuchAlgorithmException nsae) { + throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK).", nsae); + } catch (UnsupportedEncodingException uee) { + throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK).", uee); } } - -} +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/DefaultTokenServices.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/DefaultTokenServices.java index 7f6897263..0132c8882 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/DefaultTokenServices.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/DefaultTokenServices.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -13,42 +13,60 @@ package org.springframework.security.oauth2.provider.token; -import java.util.Collection; +import java.nio.charset.Charset; import java.util.Date; import java.util.Set; -import java.util.UUID; + +import org.apache.commons.codec.binary.Base64; import org.springframework.beans.factory.InitializingBean; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.crypto.keygen.BytesKeyGenerator; +import org.springframework.security.crypto.keygen.KeyGenerators; import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; -import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.ClientRegistrationException; import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.TokenRequest; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; /** - * Base implementation for token services using random UUID values for the access token and refresh token values. The + * Base implementation for token services using {@code SecureRandom} values for the access token and refresh token values. The * main extension point for customizations is the {@link TokenEnhancer} which will be called after the access and * refresh tokens have been generated but before they are stored. *

* Persistence is delegated to a {@code TokenStore} implementation and customization of the access token to a * {@link TokenEnhancer}. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Luke Taylor * @author Dave Syer */ +@Deprecated public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices, ConsumerTokenServices, InitializingBean { + private static final BytesKeyGenerator DEFAULT_TOKEN_GENERATOR = KeyGenerators.secureRandom(20); + + private static final Charset US_ASCII = Charset.forName("US-ASCII"); + private int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; // default 30 days. private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours. @@ -63,6 +81,8 @@ public class DefaultTokenServices implements AuthorizationServerTokenServices, R private TokenEnhancer accessTokenEnhancer; + private AuthenticationManager authenticationManager; + /** * Initialize these token services. If no random generator is set, one will be created. */ @@ -70,6 +90,7 @@ public void afterPropertiesSet() throws Exception { Assert.notNull(tokenStore, "tokenStore must be set"); } + @Transactional public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication); @@ -78,24 +99,30 @@ public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) if (existingAccessToken.isExpired()) { if (existingAccessToken.getRefreshToken() != null) { refreshToken = existingAccessToken.getRefreshToken(); - // The token store could remove the refresh token when the access token is removed, but we want to + // The token store could remove the refresh token when the + // access token is removed, but we want to // be sure... tokenStore.removeRefreshToken(refreshToken); } tokenStore.removeAccessToken(existingAccessToken); } else { + // Re-store the access token in case the authentication has changed + tokenStore.storeAccessToken(existingAccessToken, authentication); return existingAccessToken; } } - // Only create a new refresh token if there wasn't an existing one associated with an expired access token. - // Clients might be holding existing refresh tokens, so we re-use it in the case that the old access token + // Only create a new refresh token if there wasn't an existing one + // associated with an expired access token. + // Clients might be holding existing refresh tokens, so we re-use it in + // the case that the old access token // expired. if (refreshToken == null) { refreshToken = createRefreshToken(authentication); } - // But the refresh token itself might need to be re-issued if it has expired. + // But the refresh token itself might need to be re-issued if it has + // expired. else if (refreshToken instanceof ExpiringOAuth2RefreshToken) { ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken; if (System.currentTimeMillis() > expiring.getExpiration().getTime()) { @@ -105,6 +132,8 @@ else if (refreshToken instanceof ExpiringOAuth2RefreshToken) { OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); tokenStore.storeAccessToken(accessToken, authentication); + // In case it was modified + refreshToken = accessToken.getRefreshToken(); if (refreshToken != null) { tokenStore.storeRefreshToken(refreshToken, authentication); } @@ -112,33 +141,52 @@ else if (refreshToken instanceof ExpiringOAuth2RefreshToken) { } - public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, AuthorizationRequest request) + @Transactional(noRollbackFor={InvalidTokenException.class, InvalidGrantException.class}) + public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest) throws AuthenticationException { if (!supportRefreshToken) { - throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue); + throw new InvalidGrantException("Invalid refresh token"); } OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue); if (refreshToken == null) { - throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue); + throw new InvalidGrantException("Invalid refresh token"); } OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken); - String clientId = authentication.getAuthorizationRequest().getClientId(); - if (clientId == null || !clientId.equals(request.getClientId())) { - throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue); + if (this.authenticationManager != null && !authentication.isClientOnly()) { + // The client has already been authenticated, but the user authentication might be old now, so give it a + // chance to re-authenticate. + Authentication userAuthentication = authentication.getUserAuthentication(); + PreAuthenticatedAuthenticationToken preAuthenticatedToken = new PreAuthenticatedAuthenticationToken( + userAuthentication, + "", + authentication.getAuthorities() + ); + if (userAuthentication.getDetails() != null) { + preAuthenticatedToken.setDetails(userAuthentication.getDetails()); + } + Authentication user = authenticationManager.authenticate(preAuthenticatedToken); + Object details = authentication.getDetails(); + authentication = new OAuth2Authentication(authentication.getOAuth2Request(), user); + authentication.setDetails(details); + } + String clientId = authentication.getOAuth2Request().getClientId(); + if (clientId == null || !clientId.equals(tokenRequest.getClientId())) { + throw new InvalidGrantException("Wrong client for this refresh token"); } - // clear out any access tokens already associated with the refresh token. + // clear out any access tokens already associated with the refresh + // token. tokenStore.removeAccessTokenUsingRefreshToken(refreshToken); if (isExpired(refreshToken)) { tokenStore.removeRefreshToken(refreshToken); - throw new InvalidTokenException("Invalid refresh token (expired): " + refreshToken); + throw new InvalidTokenException("Invalid refresh token (expired)"); } - authentication = createRefreshedAuthentication(authentication, request.getScope()); + authentication = createRefreshedAuthentication(authentication, tokenRequest); if (!reuseRefreshToken) { tokenStore.removeRefreshToken(refreshToken); @@ -148,7 +196,7 @@ public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, Authorizat OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); tokenStore.storeAccessToken(accessToken, authentication); if (!reuseRefreshToken) { - tokenStore.storeRefreshToken(refreshToken, authentication); + tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication); } return accessToken; } @@ -161,23 +209,24 @@ public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { * Create a refreshed authentication. * * @param authentication The authentication. - * @param scope The scope for the refreshed token. + * @param request The scope for the refreshed token. * @return The refreshed authentication. * @throws InvalidScopeException If the scope requested is invalid or wider than the original scope. */ - private OAuth2Authentication createRefreshedAuthentication(OAuth2Authentication authentication, Set scope) { + private OAuth2Authentication createRefreshedAuthentication(OAuth2Authentication authentication, TokenRequest request) { OAuth2Authentication narrowed = authentication; + Set scope = request.getScope(); + OAuth2Request clientAuth = authentication.getOAuth2Request().refresh(request); if (scope != null && !scope.isEmpty()) { - AuthorizationRequest clientAuth = authentication.getAuthorizationRequest(); Set originalScope = clientAuth.getScope(); if (originalScope == null || !originalScope.containsAll(scope)) { - throw new InvalidScopeException("Unable to narrow the scope of the client authentication to " + scope - + ".", originalScope); + throw new InvalidScopeException("Unable to narrow the scope of the client authentication", originalScope); } else { - narrowed = new OAuth2Authentication(clientAuth, authentication.getUserAuthentication()); + clientAuth = clientAuth.narrowScope(scope); } } + narrowed = new OAuth2Authentication(clientAuth, authentication.getUserAuthentication()); return narrowed; } @@ -194,38 +243,44 @@ public OAuth2AccessToken readAccessToken(String accessToken) { return tokenStore.readAccessToken(accessToken); } - public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException { + public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException, + InvalidTokenException { OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue); if (accessToken == null) { - throw new InvalidTokenException("Invalid access token: " + accessTokenValue); + throw new InvalidTokenException("Invalid access token"); } else if (accessToken.isExpired()) { tokenStore.removeAccessToken(accessToken); - throw new InvalidTokenException("Access token expired: " + accessTokenValue); + throw new InvalidTokenException("Access token expired"); } OAuth2Authentication result = tokenStore.readAuthentication(accessToken); + if (result == null) { + // in case of race condition + throw new InvalidTokenException("Invalid access token"); + } + if (clientDetailsService != null) { + String clientId = result.getOAuth2Request().getClientId(); + try { + clientDetailsService.loadClientByClientId(clientId); + } + catch (ClientRegistrationException e) { + throw new InvalidTokenException("Client not valid", e); + } + } return result; } public String getClientId(String tokenValue) { OAuth2Authentication authentication = tokenStore.readAuthentication(tokenValue); if (authentication == null) { - throw new InvalidTokenException("Invalid access token: " + tokenValue); + throw new InvalidTokenException("Invalid access token"); } - AuthorizationRequest authorizationRequest = authentication.getAuthorizationRequest(); - if (authorizationRequest == null) { - throw new InvalidTokenException("Invalid access token (no client id): " + tokenValue); + OAuth2Request clientAuth = authentication.getOAuth2Request(); + if (clientAuth == null) { + throw new InvalidTokenException("Invalid access token (no client id)"); } - return authorizationRequest.getClientId(); - } - - public Collection findTokensByUserName(String userName) { - return tokenStore.findTokensByUserName(userName); - } - - public Collection findTokensByClientId(String clientId) { - return tokenStore.findTokensByClientId(clientId); + return clientAuth.getClientId(); } public boolean revokeToken(String tokenValue) { @@ -240,36 +295,41 @@ public boolean revokeToken(String tokenValue) { return true; } - private ExpiringOAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) { - if (!isSupportRefreshToken(authentication.getAuthorizationRequest())) { + private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) { + if (!isSupportRefreshToken(authentication.getOAuth2Request())) { return null; } - int validitySeconds = getRefreshTokenValiditySeconds(authentication.getAuthorizationRequest()); - ExpiringOAuth2RefreshToken refreshToken = new DefaultExpiringOAuth2RefreshToken(UUID.randomUUID().toString(), - new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); - return refreshToken; + int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request()); + String tokenValue = new String(Base64.encodeBase64URLSafe(DEFAULT_TOKEN_GENERATOR.generateKey()), US_ASCII); + if (validitySeconds > 0) { + return new DefaultExpiringOAuth2RefreshToken(tokenValue, new Date(System.currentTimeMillis() + + (validitySeconds * 1000L))); + } + return new DefaultOAuth2RefreshToken(tokenValue); } private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) { - DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString()); - int validitySeconds = getAccessTokenValiditySeconds(authentication.getAuthorizationRequest()); + String tokenValue = new String(Base64.encodeBase64URLSafe(DEFAULT_TOKEN_GENERATOR.generateKey()), US_ASCII); + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(tokenValue); + int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request()); if (validitySeconds > 0) { token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); } token.setRefreshToken(refreshToken); - token.setScope(authentication.getAuthorizationRequest().getScope()); + token.setScope(authentication.getOAuth2Request().getScope()); return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token; } /** * The access token validity period in seconds - * @param authorizationRequest the current authorization request + * + * @param clientAuth the current authorization request * @return the access token validity period in seconds */ - protected int getAccessTokenValiditySeconds(AuthorizationRequest authorizationRequest) { + protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) { if (clientDetailsService != null) { - ClientDetails client = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId()); + ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); Integer validity = client.getAccessTokenValiditySeconds(); if (validity != null) { return validity; @@ -280,12 +340,13 @@ protected int getAccessTokenValiditySeconds(AuthorizationRequest authorizationRe /** * The refresh token validity period in seconds - * @param authorizationRequest the current authorization request + * + * @param clientAuth the current authorization request * @return the refresh token validity period in seconds */ - protected int getRefreshTokenValiditySeconds(AuthorizationRequest authorizationRequest) { + protected int getRefreshTokenValiditySeconds(OAuth2Request clientAuth) { if (clientDetailsService != null) { - ClientDetails client = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId()); + ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); Integer validity = client.getRefreshTokenValiditySeconds(); if (validity != null) { return validity; @@ -297,12 +358,13 @@ protected int getRefreshTokenValiditySeconds(AuthorizationRequest authorizationR /** * Is a refresh token supported for this client (or the global setting if * {@link #setClientDetailsService(ClientDetailsService) clientDetailsService} is not set. - * @param authorizationRequest the current authorization request + * + * @param clientAuth the current authorization request * @return boolean to indicate if refresh token is supported */ - protected boolean isSupportRefreshToken(AuthorizationRequest authorizationRequest) { + protected boolean isSupportRefreshToken(OAuth2Request clientAuth) { if (clientDetailsService != null) { - ClientDetails client = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId()); + ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); return client.getAuthorizedGrantTypes().contains("refresh_token"); } return this.supportRefreshToken; @@ -318,7 +380,8 @@ public void setTokenEnhancer(TokenEnhancer accessTokenEnhancer) { } /** - * The validity (in seconds) of the refresh token. + * The validity (in seconds) of the refresh token. If less than or equal to zero then the tokens will be + * non-expiring. * * @param refreshTokenValiditySeconds The validity (in seconds) of the refresh token. */ @@ -328,7 +391,7 @@ public void setRefreshTokenValiditySeconds(int refreshTokenValiditySeconds) { /** * The default validity (in seconds) of the access token. Zero or negative for non-expiring tokens. If a client - * details service is set the validity period will be read from he client, defaulting to this value if not defined + * details service is set the validity period will be read from the client, defaulting to this value if not defined * by the client. * * @param accessTokenValiditySeconds The validity (in seconds) of the access token. @@ -364,6 +427,16 @@ public void setTokenStore(TokenStore tokenStore) { this.tokenStore = tokenStore; } + /** + * An authentication manager that will be used (if provided) to check the user authentication when a token is + * refreshed. + * + * @param authenticationManager the authenticationManager to set + */ + public void setAuthenticationManager(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + /** * The client details service to use for looking up clients (if necessary). Optional if the access token expiry is * set globally via {@link #setAccessTokenValiditySeconds(int)}. diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/DefaultUserAuthenticationConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/DefaultUserAuthenticationConverter.java new file mode 100644 index 000000000..331114567 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/DefaultUserAuthenticationConverter.java @@ -0,0 +1,114 @@ +/* + * Cloud Foundry 2012.02.03 Beta + * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + */ + +package org.springframework.security.oauth2.provider.token; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.util.StringUtils; + +/** + * Default implementation of {@link UserAuthenticationConverter}. Converts to and from an Authentication using only its + * name and authorities. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class DefaultUserAuthenticationConverter implements UserAuthenticationConverter { + + private Collection defaultAuthorities; + + private UserDetailsService userDetailsService; + + private String userClaimName = USERNAME; + + /** + * Optional {@link UserDetailsService} to use when extracting an {@link Authentication} from the incoming map. + * + * @param userDetailsService the userDetailsService to set + */ + public void setUserDetailsService(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + /** + * Set the name of the user claim to use when extracting an {@link Authentication} from the incoming map + * or when converting an {@link Authentication} to a map. + * @param claimName the claim name to use (default {@link UserAuthenticationConverter#USERNAME}) + */ + public void setUserClaimName(String claimName) { + this.userClaimName = claimName; + } + + /** + * Default value for authorities if an Authentication is being created and the input has no data for authorities. + * Note that unless this property is set, the default Authentication created by {@link #extractAuthentication(Map)} + * will be unauthenticated. + * + * @param defaultAuthorities the defaultAuthorities to set. Default null. + */ + public void setDefaultAuthorities(String[] defaultAuthorities) { + this.defaultAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils + .arrayToCommaDelimitedString(defaultAuthorities)); + } + + public Map convertUserAuthentication(Authentication authentication) { + Map response = new LinkedHashMap(); + response.put(userClaimName, authentication.getName()); + if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) { + response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities())); + } + return response; + } + + public Authentication extractAuthentication(Map map) { + if (map.containsKey(userClaimName)) { + Object principal = map.get(userClaimName); + Collection authorities = getAuthorities(map); + if (userDetailsService != null) { + UserDetails user = userDetailsService.loadUserByUsername((String) map.get(userClaimName)); + authorities = user.getAuthorities(); + principal = user; + } + return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities); + } + return null; + } + + protected Collection getAuthorities(Map map) { + if (!map.containsKey(AUTHORITIES)) { + return defaultAuthorities; + } + Object authorities = map.get(AUTHORITIES); + if (authorities instanceof String) { + return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities); + } + if (authorities instanceof Collection) { + return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils + .collectionToCommaDelimitedString((Collection) authorities)); + } + throw new IllegalArgumentException("Authorities must be either a String or a Collection"); + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/RemoteTokenServices.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/RemoteTokenServices.java new file mode 100644 index 000000000..db5095892 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/RemoteTokenServices.java @@ -0,0 +1,179 @@ +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + *******************************************************************************/ +package org.springframework.security.oauth2.provider.token; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.DefaultResponseErrorHandler; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Map; + +/** + * Queries the /check_token endpoint to obtain the contents of an access token. + * + * If the endpoint returns a 400 response, this indicates that the token is invalid. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * @author Luke Taylor + * @author Mathieu Ouellet + * + */ +@Deprecated +public class RemoteTokenServices implements ResourceServerTokenServices { + + protected final Log logger = LogFactory.getLog(getClass()); + + private RestOperations restTemplate; + + private String checkTokenEndpointUrl; + + private String clientId; + + private String clientSecret; + + private String tokenName = "token"; + + private Map additionalParameters; + + private AccessTokenConverter tokenConverter = new DefaultAccessTokenConverter(); + + public RemoteTokenServices() { + restTemplate = new RestTemplate(); + ((RestTemplate) restTemplate).setErrorHandler(new DefaultResponseErrorHandler() { + @Override + // Ignore 400 + public void handleError(ClientHttpResponse response) throws IOException { + if (response.getRawStatusCode() != 400) { + super.handleError(response); + } + } + }); + } + + public void setRestTemplate(RestOperations restTemplate) { + this.restTemplate = restTemplate; + } + + public void setCheckTokenEndpointUrl(String checkTokenEndpointUrl) { + this.checkTokenEndpointUrl = checkTokenEndpointUrl; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public void setAccessTokenConverter(AccessTokenConverter accessTokenConverter) { + this.tokenConverter = accessTokenConverter; + } + + public void setTokenName(String tokenName) { + this.tokenName = tokenName; + } + + public void setAdditionalParameters(Map additionalParameters) { + this.additionalParameters = additionalParameters; + } + + @Override + public OAuth2Authentication loadAuthentication(String accessToken) + throws AuthenticationException, InvalidTokenException { + + MultiValueMap formData = new LinkedMultiValueMap(); + if (additionalParameters != null) { + formData.setAll(additionalParameters); + } + formData.add(tokenName, accessToken); + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret)); + Map map = postForMap(checkTokenEndpointUrl, formData, headers); + + if (CollectionUtils.isEmpty(map)) { + if (logger.isDebugEnabled()) { + logger.debug("check_token returned empty"); + } + throw new InvalidTokenException(accessToken); + } + + if (map.containsKey("error")) { + if (logger.isDebugEnabled()) { + logger.debug("check_token returned error: " + map.get("error")); + } + throw new InvalidTokenException(accessToken); + } + + // gh-838 + if (map.containsKey("active") && !"true".equals(String.valueOf(map.get("active")))) { + logger.debug("check_token returned active attribute: " + map.get("active")); + throw new InvalidTokenException(accessToken); + } + + return tokenConverter.extractAuthentication(map); + } + + @Override + public OAuth2AccessToken readAccessToken(String accessToken) { + throw new UnsupportedOperationException("Not supported: read access token"); + } + + private String getAuthorizationHeader(String clientId, String clientSecret) { + + if(clientId == null || clientSecret == null) { + logger.warn("Null Client ID or Client Secret detected. Endpoint that requires authentication will reject request with 401 error."); + } + + String creds = String.format("%s:%s", clientId, clientSecret); + try { + return "Basic " + new String(Base64.encode(creds.getBytes("UTF-8"))); + } + catch (UnsupportedEncodingException e) { + throw new IllegalStateException("Could not convert String"); + } + } + + private Map postForMap(String path, MultiValueMap formData, HttpHeaders headers) { + if (headers.getContentType() == null) { + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + } + @SuppressWarnings("rawtypes") + Map map = restTemplate.exchange(path, HttpMethod.POST, + new HttpEntity>(formData, headers), Map.class).getBody(); + @SuppressWarnings("unchecked") + Map result = map; + return result; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/ResourceServerTokenServices.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/ResourceServerTokenServices.java index 4db99308f..6af3f7f21 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/ResourceServerTokenServices.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/ResourceServerTokenServices.java @@ -2,8 +2,15 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.provider.OAuth2Authentication; +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + */ +@Deprecated public interface ResourceServerTokenServices { /** @@ -12,8 +19,9 @@ public interface ResourceServerTokenServices { * @param accessToken The access token value. * @return The authentication for the access token. * @throws AuthenticationException If the access token is expired + * @throws InvalidTokenException if the token isn't valid */ - OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException; + OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException; /** * Retrieve the full access token details from just the value. diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/TokenEnhancer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/TokenEnhancer.java index 6da146dfa..e33122fd6 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/TokenEnhancer.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/TokenEnhancer.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -18,10 +18,14 @@ /** * Strategy for enhancing an access token before it is stored by an {@link AuthorizationServerTokenServices} * implementation. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public interface TokenEnhancer { /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/TokenEnhancerChain.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/TokenEnhancerChain.java index d5dc50c4f..e9fe8b5e1 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/TokenEnhancerChain.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/TokenEnhancerChain.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -20,10 +20,14 @@ /** * A composite token enhancer that loops over its delegate enhancers. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class TokenEnhancerChain implements TokenEnhancer { private List delegates = Collections.emptyList(); diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/TokenStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/TokenStore.java index da106141b..8cf2072a8 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/TokenStore.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/TokenStore.java @@ -8,18 +8,23 @@ /** * Persistence interface for OAuth2 tokens. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * */ +@Deprecated public interface TokenStore { - /** - * Read the authentication stored under the specified token value. - * - * @param token The token value under which the authentication is stored. - * @return The authentication, or null if none. - */ - OAuth2Authentication readAuthentication(OAuth2AccessToken token); - - /** + /** + * Read the authentication stored under the specified token value. + * + * @param token The token value under which the authentication is stored. + * @return The authentication, or null if none. + */ + OAuth2Authentication readAuthentication(OAuth2AccessToken token); + + /** * Read the authentication stored under the specified token value. * * @param token The token value under which the authentication is stored. @@ -44,14 +49,14 @@ public interface TokenStore { OAuth2AccessToken readAccessToken(String tokenValue); /** - * Remove an access token from the database. + * Remove an access token from the store. * - * @param token The token to remove from the database. + * @param token The token to remove from the store. */ void removeAccessToken(OAuth2AccessToken token); /** - * Store the specified refresh token in the database. + * Store the specified refresh token in the store. * * @param refreshToken The refresh token to store. * @param authentication The authentication associated with the refresh token. @@ -73,9 +78,9 @@ public interface TokenStore { OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token); /** - * Remove a refresh token from the database. + * Remove a refresh token from the store. * - * @param token The token to remove from the database. + * @param token The token to remove from the store. */ void removeRefreshToken(OAuth2RefreshToken token); @@ -97,13 +102,14 @@ public interface TokenStore { OAuth2AccessToken getAccessToken(OAuth2Authentication authentication); /** + * @param clientId the client id to search * @param userName the user name to search * @return a collection of access tokens */ - Collection findTokensByUserName(String userName); + Collection findTokensByClientIdAndUserName(String clientId, String userName); /** - * @param clientId the client id + * @param clientId the client id to search * @return a collection of access tokens */ Collection findTokensByClientId(String clientId); diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/UserAuthenticationConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/UserAuthenticationConverter.java new file mode 100644 index 000000000..1403d5bf7 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/UserAuthenticationConverter.java @@ -0,0 +1,52 @@ +/* + * Cloud Foundry 2012.02.03 Beta + * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + */ + +package org.springframework.security.oauth2.provider.token; + +import java.util.Map; + +import org.springframework.security.core.Authentication; + +/** + * Utility interface for converting a user authentication to and from a Map. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public interface UserAuthenticationConverter { + + final String AUTHORITIES = AccessTokenConverter.AUTHORITIES; + + final String USERNAME = "user_name"; + + /** + * Extract information about the user to be used in an access token (i.e. for resource servers). + * + * @param userAuthentication an authentication representing a user + * @return a map of key values representing the unique information about the user + */ + Map convertUserAuthentication(Authentication userAuthentication); + + /** + * Inverse of {@link #convertUserAuthentication(Authentication)}. Extracts an Authentication from a map. + * + * @param map a map of user information + * @return an Authentication representing the user or null if there is none + */ + Authentication extractAuthentication(Map map); + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/DelegatingJwtClaimsSetVerifier.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/DelegatingJwtClaimsSetVerifier.java new file mode 100644 index 000000000..9d60b24de --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/DelegatingJwtClaimsSetVerifier.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.token.store; + +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * A {@link JwtClaimsSetVerifier} that delegates claims verification + * to it's internal List of {@link JwtClaimsSetVerifier}'s. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Joe Grandja + * @since 2.2 + * @see JwtClaimsSetVerifier + */ +@Deprecated +public class DelegatingJwtClaimsSetVerifier implements JwtClaimsSetVerifier { + private final List jwtClaimsSetVerifiers; + + public DelegatingJwtClaimsSetVerifier(List jwtClaimsSetVerifiers) { + Assert.notEmpty(jwtClaimsSetVerifiers, "jwtClaimsSetVerifiers cannot be empty"); + this.jwtClaimsSetVerifiers = Collections.unmodifiableList(new ArrayList(jwtClaimsSetVerifiers)); + } + + @Override + public void verify(Map claims) throws InvalidTokenException { + for (JwtClaimsSetVerifier jwtClaimsSetVerifier : this.jwtClaimsSetVerifiers) { + jwtClaimsSetVerifier.verify(claims); + } + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/InMemoryTokenStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/InMemoryTokenStore.java similarity index 81% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/InMemoryTokenStore.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/InMemoryTokenStore.java index 14226ac4d..7f30f4d36 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/InMemoryTokenStore.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/InMemoryTokenStore.java @@ -1,4 +1,4 @@ -package org.springframework.security.oauth2.provider.token; +package org.springframework.security.oauth2.provider.token.store; import java.util.Collection; import java.util.Collections; @@ -13,15 +13,22 @@ import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator; +import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator; +import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.util.Assert; /** * Implementation of token services that stores tokens in memory. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ryan Heaton * @author Luke Taylor * @author Dave Syer */ +@Deprecated public class InMemoryTokenStore implements TokenStore { private static final int DEFAULT_FLUSH_INTERVAL = 1000; @@ -42,7 +49,7 @@ public class InMemoryTokenStore implements TokenStore { private final ConcurrentHashMap refreshTokenAuthenticationStore = new ConcurrentHashMap(); - private final ConcurrentHashMap refreshTokenToAcessTokenStore = new ConcurrentHashMap(); + private final ConcurrentHashMap refreshTokenToAccessTokenStore = new ConcurrentHashMap(); private final DelayQueue expiryQueue = new DelayQueue(); @@ -78,13 +85,12 @@ public int getFlushInterval() { public void clear() { accessTokenStore.clear(); authenticationToAccessTokenStore.clear(); - userNameToAccessTokenStore.clear(); clientIdToAccessTokenStore.clear(); refreshTokenStore.clear(); accessTokenToRefreshTokenStore.clear(); authenticationStore.clear(); refreshTokenAuthenticationStore.clear(); - refreshTokenToAcessTokenStore.clear(); + refreshTokenToAccessTokenStore.clear(); expiryQueue.clear(); } @@ -102,7 +108,7 @@ public int getAccessTokenCount() { } public int getRefreshTokenCount() { - Assert.state(refreshTokenStore.size() == refreshTokenToAcessTokenStore.size(), + Assert.state(refreshTokenStore.size() == refreshTokenToAccessTokenStore.size(), "Inconsistent refresh token store state"); return accessTokenStore.size(); } @@ -112,9 +118,10 @@ public int getExpiryTokenCount() { } public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { - OAuth2AccessToken accessToken = authenticationToAccessTokenStore.get(authenticationKeyGenerator - .extractKey(authentication)); - if (accessToken != null && !authentication.equals(readAuthentication(accessToken.getValue()))) { + String key = authenticationKeyGenerator.extractKey(authentication); + OAuth2AccessToken accessToken = authenticationToAccessTokenStore.get(key); + if (accessToken != null + && !key.equals(authenticationKeyGenerator.extractKey(readAuthentication(accessToken.getValue())))) { // Keep the stores consistent (maybe the same user is represented by this authentication but the details // have changed) storeAccessToken(accessToken, authentication); @@ -147,9 +154,9 @@ public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authe this.authenticationStore.put(token.getValue(), authentication); this.authenticationToAccessTokenStore.put(authenticationKeyGenerator.extractKey(authentication), token); if (!authentication.isClientOnly()) { - addToCollection(this.userNameToAccessTokenStore, authentication.getName(), token); + addToCollection(this.userNameToAccessTokenStore, getApprovalKey(authentication), token); } - addToCollection(this.clientIdToAccessTokenStore, authentication.getAuthorizationRequest().getClientId(), token); + addToCollection(this.clientIdToAccessTokenStore, authentication.getOAuth2Request().getClientId(), token); if (token.getExpiration() != null) { TokenExpiry expiry = new TokenExpiry(token.getValue(), token.getExpiration()); // Remove existing expiry for this token if present @@ -157,11 +164,21 @@ public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authe this.expiryQueue.put(expiry); } if (token.getRefreshToken() != null && token.getRefreshToken().getValue() != null) { - this.refreshTokenToAcessTokenStore.put(token.getRefreshToken().getValue(), token.getValue()); + this.refreshTokenToAccessTokenStore.put(token.getRefreshToken().getValue(), token.getValue()); this.accessTokenToRefreshTokenStore.put(token.getValue(), token.getRefreshToken().getValue()); } } + private String getApprovalKey(OAuth2Authentication authentication) { + String userName = authentication.getUserAuthentication() == null ? "" : authentication.getUserAuthentication() + .getName(); + return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName); + } + + private String getApprovalKey(String clientId, String userName) { + return clientId + (userName==null ? "" : ":" + userName); + } + private void addToCollection(ConcurrentHashMap> store, String key, OAuth2AccessToken token) { if (!store.containsKey(key)) { @@ -184,20 +201,17 @@ public OAuth2AccessToken readAccessToken(String tokenValue) { public void removeAccessToken(String tokenValue) { OAuth2AccessToken removed = this.accessTokenStore.remove(tokenValue); - String refresh = this.accessTokenToRefreshTokenStore.remove(tokenValue); - if (refresh != null) { - // Don't remove the refresh token itself - it's up to the caller to do that - this.refreshTokenToAcessTokenStore.remove(tokenValue); - } + this.accessTokenToRefreshTokenStore.remove(tokenValue); + // Don't remove the refresh token - it's up to the caller to do that OAuth2Authentication authentication = this.authenticationStore.remove(tokenValue); if (authentication != null) { this.authenticationToAccessTokenStore.remove(authenticationKeyGenerator.extractKey(authentication)); Collection tokens; - tokens = this.userNameToAccessTokenStore.get(authentication.getName()); + String clientId = authentication.getOAuth2Request().getClientId(); + tokens = this.userNameToAccessTokenStore.get(getApprovalKey(clientId, authentication.getName())); if (tokens != null) { tokens.remove(removed); } - String clientId = authentication.getAuthorizationRequest().getClientId(); tokens = this.clientIdToAccessTokenStore.get(clientId); if (tokens != null) { tokens.remove(removed); @@ -222,7 +236,7 @@ public void removeRefreshToken(OAuth2RefreshToken refreshToken) { public void removeRefreshToken(String tokenValue) { this.refreshTokenStore.remove(tokenValue); this.refreshTokenAuthenticationStore.remove(tokenValue); - this.refreshTokenToAcessTokenStore.remove(tokenValue); + this.refreshTokenToAccessTokenStore.remove(tokenValue); } public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { @@ -230,20 +244,20 @@ public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) } private void removeAccessTokenUsingRefreshToken(String refreshToken) { - String accessToken = this.refreshTokenToAcessTokenStore.remove(refreshToken); + String accessToken = this.refreshTokenToAccessTokenStore.remove(refreshToken); if (accessToken != null) { removeAccessToken(accessToken); } } - public Collection findTokensByClientId(String clientId) { - Collection result = clientIdToAccessTokenStore.get(clientId); + public Collection findTokensByClientIdAndUserName(String clientId, String userName) { + Collection result = userNameToAccessTokenStore.get(getApprovalKey(clientId, userName)); return result != null ? Collections. unmodifiableCollection(result) : Collections . emptySet(); } - public Collection findTokensByUserName(String userName) { - Collection result = userNameToAccessTokenStore.get(userName); + public Collection findTokensByClientId(String clientId) { + Collection result = clientIdToAccessTokenStore.get(clientId); return result != null ? Collections. unmodifiableCollection(result) : Collections . emptySet(); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/IssuerClaimVerifier.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/IssuerClaimVerifier.java new file mode 100644 index 000000000..4a206382e --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/IssuerClaimVerifier.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.token.store; + +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +import java.net.URL; +import java.util.Map; + +/** + * A {@link JwtClaimsSetVerifier} that verifies the Issuer (iss) claim contained in the + * JWT Claims Set against the issuer supplied to the constructor. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Joe Grandja + * @since 2.2 + * @see JwtClaimsSetVerifier + */ +@Deprecated +public class IssuerClaimVerifier implements JwtClaimsSetVerifier { + private static final String ISS_CLAIM = "iss"; + private final URL issuer; + + public IssuerClaimVerifier(URL issuer) { + Assert.notNull(issuer, "issuer cannot be null"); + this.issuer = issuer; + } + + @Override + public void verify(Map claims) throws InvalidTokenException { + if (!CollectionUtils.isEmpty(claims) && claims.containsKey(ISS_CLAIM)) { + String jwtIssuer = (String)claims.get(ISS_CLAIM); + if (!jwtIssuer.equals(this.issuer.toString())) { + throw new InvalidTokenException("Invalid Issuer (iss) claim: " + jwtIssuer); + } + } + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/JdbcTokenStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/JdbcTokenStore.java similarity index 79% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/JdbcTokenStore.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/JdbcTokenStore.java index 58174a4a3..46305c2ad 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/JdbcTokenStore.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/JdbcTokenStore.java @@ -1,4 +1,4 @@ -package org.springframework.security.oauth2.provider.token; +package org.springframework.security.oauth2.provider.token.store; import java.io.UnsupportedEncodingException; import java.math.BigInteger; @@ -23,15 +23,22 @@ import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.util.SerializationUtils; import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator; +import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator; +import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.util.Assert; /** * Implementation of token services that stores tokens in a database. - * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Ken Dombeck * @author Luke Taylor * @author Dave Syer */ +@Deprecated public class JdbcTokenStore implements TokenStore { private static final Log LOG = LogFactory.getLog(JdbcTokenStore.class); @@ -44,6 +51,8 @@ public class JdbcTokenStore implements TokenStore { private static final String DEFAULT_ACCESS_TOKEN_FROM_AUTHENTICATION_SELECT_STATEMENT = "select token_id, token from oauth_access_token where authentication_id = ?"; + private static final String DEFAULT_ACCESS_TOKENS_FROM_USERNAME_AND_CLIENT_SELECT_STATEMENT = "select token_id, token from oauth_access_token where user_name = ? and client_id = ?"; + private static final String DEFAULT_ACCESS_TOKENS_FROM_USERNAME_SELECT_STATEMENT = "select token_id, token from oauth_access_token where user_name = ?"; private static final String DEFAULT_ACCESS_TOKENS_FROM_CLIENTID_SELECT_STATEMENT = "select token_id, token from oauth_access_token where client_id = ?"; @@ -68,6 +77,8 @@ public class JdbcTokenStore implements TokenStore { private String selectAccessTokenFromAuthenticationSql = DEFAULT_ACCESS_TOKEN_FROM_AUTHENTICATION_SELECT_STATEMENT; + private String selectAccessTokensFromUserNameAndClientIdSql = DEFAULT_ACCESS_TOKENS_FROM_USERNAME_AND_CLIENT_SELECT_STATEMENT; + private String selectAccessTokensFromUserNameSql = DEFAULT_ACCESS_TOKENS_FROM_USERNAME_SELECT_STATEMENT; private String selectAccessTokensFromClientIdSql = DEFAULT_ACCESS_TOKENS_FROM_CLIENTID_SELECT_STATEMENT; @@ -100,27 +111,32 @@ public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticat public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { OAuth2AccessToken accessToken = null; + String key = authenticationKeyGenerator.extractKey(authentication); try { accessToken = jdbcTemplate.queryForObject(selectAccessTokenFromAuthenticationSql, new RowMapper() { public OAuth2AccessToken mapRow(ResultSet rs, int rowNum) throws SQLException { return deserializeAccessToken(rs.getBytes(2)); } - }, authenticationKeyGenerator.extractKey(authentication)); + }, key); } catch (EmptyResultDataAccessException e) { - if (LOG.isInfoEnabled()) { + if (LOG.isDebugEnabled()) { LOG.debug("Failed to find access token for authentication " + authentication); } - } catch (IllegalArgumentException e) { - LOG.error("Could not extract access token for authentication " + authentication); + } + catch (IllegalArgumentException e) { + LOG.error("Could not extract access token for authentication " + authentication, e); } - if (accessToken != null && !authentication.equals(readAuthentication(accessToken.getValue()))) { - removeAccessToken(accessToken.getValue()); - // Keep the store consistent (maybe the same user is represented by this authentication but the details have - // changed) - storeAccessToken(accessToken, authentication); + if (accessToken != null) { + OAuth2Authentication oldAuthentication = readAuthentication(accessToken.getValue()); + if (oldAuthentication == null || !key.equals(authenticationKeyGenerator.extractKey(oldAuthentication))) { + removeAccessToken(accessToken.getValue()); + // Keep the store consistent (maybe the same user is represented by this authentication but the details have + // changed) + storeAccessToken(accessToken, authentication); + } } return accessToken; } @@ -130,13 +146,17 @@ public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authe if (token.getRefreshToken() != null) { refreshToken = token.getRefreshToken().getValue(); } + + if (readAccessToken(token.getValue())!=null) { + removeAccessToken(token.getValue()); + } jdbcTemplate.update(insertAccessTokenSql, new Object[] { extractTokenKey(token.getValue()), new SqlLobValue(serializeAccessToken(token)), authenticationKeyGenerator.extractKey(authentication), authentication.isClientOnly() ? null : authentication.getName(), - authentication.getAuthorizationRequest().getClientId(), - new SqlLobValue(serializeAuthentication(authentication)), extractTokenKey(refreshToken) }, new int[] { Types.VARCHAR, - Types.BLOB, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.BLOB, Types.VARCHAR }); + authentication.getOAuth2Request().getClientId(), + new SqlLobValue(serializeAuthentication(authentication)), extractTokenKey(refreshToken) }, new int[] { + Types.VARCHAR, Types.BLOB, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.BLOB, Types.VARCHAR }); } public OAuth2AccessToken readAccessToken(String tokenValue) { @@ -151,10 +171,11 @@ public OAuth2AccessToken mapRow(ResultSet rs, int rowNum) throws SQLException { } catch (EmptyResultDataAccessException e) { if (LOG.isInfoEnabled()) { - LOG.info("Failed to find access token for token " + tokenValue); + LOG.info("Failed to find access token"); } - } catch (IllegalArgumentException e) { - LOG.warn("Failed to deserialize access token for " + tokenValue); + } + catch (IllegalArgumentException e) { + LOG.warn("Failed to deserialize access token", e); removeAccessToken(tokenValue); } @@ -186,10 +207,11 @@ public OAuth2Authentication mapRow(ResultSet rs, int rowNum) throws SQLException } catch (EmptyResultDataAccessException e) { if (LOG.isInfoEnabled()) { - LOG.info("Failed to find access token for token " + token); + LOG.info("Failed to find access token"); } - } catch (IllegalArgumentException e) { - LOG.warn("Failed to deserialize authentication for " + token); + } + catch (IllegalArgumentException e) { + LOG.warn("Failed to deserialize authentication", e); removeAccessToken(token); } @@ -215,10 +237,11 @@ public OAuth2RefreshToken mapRow(ResultSet rs, int rowNum) throws SQLException { } catch (EmptyResultDataAccessException e) { if (LOG.isInfoEnabled()) { - LOG.info("Failed to find refresh token for token " + token); + LOG.info("Failed to find refresh token"); } - } catch (IllegalArgumentException e) { - LOG.warn("Failed to deserialize refresh token for token " + token); + } + catch (IllegalArgumentException e) { + LOG.warn("Failed to deserialize refresh token", e); removeRefreshToken(token); } @@ -250,10 +273,11 @@ public OAuth2Authentication mapRow(ResultSet rs, int rowNum) throws SQLException } catch (EmptyResultDataAccessException e) { if (LOG.isInfoEnabled()) { - LOG.info("Failed to find access token for token " + value); + LOG.info("Failed to find access token"); } - } catch (IllegalArgumentException e) { - LOG.warn("Failed to deserialize access token for " + value); + } + catch (IllegalArgumentException e) { + LOG.warn("Failed to deserialize access token", e); removeRefreshToken(value); } @@ -273,7 +297,8 @@ public Collection findTokensByClientId(String clientId) { List accessTokens = new ArrayList(); try { - accessTokens = jdbcTemplate.query(selectAccessTokensFromClientIdSql, new SafeAccessTokenRowMapper(), clientId); + accessTokens = jdbcTemplate.query(selectAccessTokensFromClientIdSql, new SafeAccessTokenRowMapper(), + clientId); } catch (EmptyResultDataAccessException e) { if (LOG.isInfoEnabled()) { @@ -289,11 +314,28 @@ public Collection findTokensByUserName(String userName) { List accessTokens = new ArrayList(); try { - accessTokens = jdbcTemplate.query(selectAccessTokensFromUserNameSql, new SafeAccessTokenRowMapper(), userName); + accessTokens = jdbcTemplate.query(selectAccessTokensFromUserNameSql, new SafeAccessTokenRowMapper(), + userName); } catch (EmptyResultDataAccessException e) { - if (LOG.isInfoEnabled()) { + if (LOG.isInfoEnabled()) LOG.info("Failed to find access token for userName " + userName); + } + accessTokens = removeNulls(accessTokens); + + return accessTokens; + } + + public Collection findTokensByClientIdAndUserName(String clientId, String userName) { + List accessTokens = new ArrayList(); + + try { + accessTokens = jdbcTemplate.query(selectAccessTokensFromUserNameAndClientIdSql, new SafeAccessTokenRowMapper(), + userName, clientId); + } + catch (EmptyResultDataAccessException e) { + if (LOG.isInfoEnabled()) { + LOG.info("Failed to find access token for clientId " + clientId + " and userName " + userName); } } accessTokens = removeNulls(accessTokens); @@ -304,7 +346,7 @@ public Collection findTokensByUserName(String userName) { private List removeNulls(List accessTokens) { List tokens = new ArrayList(); for (OAuth2AccessToken token : accessTokens) { - if (token!=null) { + if (token != null) { tokens.add(token); } } @@ -312,7 +354,7 @@ private List removeNulls(List accessTokens } protected String extractTokenKey(String value) { - if (value==null) { + if (value == null) { return null; } MessageDigest digest; @@ -336,7 +378,8 @@ private final class SafeAccessTokenRowMapper implements RowMapper + * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @see TokenEnhancer + * @see AccessTokenConverter + * + * @author Dave Syer + * @author Luke Taylor + */ +@Deprecated +public class JwtAccessTokenConverter implements TokenEnhancer, AccessTokenConverter, InitializingBean { + + /** + * Field name for token id. + */ + public static final String TOKEN_ID = AccessTokenConverter.JTI; + + /** + * Field name for access token id. + */ + public static final String ACCESS_TOKEN_ID = AccessTokenConverter.ATI; + + private static final Log logger = LogFactory.getLog(JwtAccessTokenConverter.class); + + private AccessTokenConverter tokenConverter = new DefaultAccessTokenConverter(); + + private JwtClaimsSetVerifier jwtClaimsSetVerifier = new NoOpJwtClaimsSetVerifier(); + + private JsonParser objectMapper = JsonParserFactory.create(); + + private String verifierKey = new RandomValueStringGenerator().generate(); + + private Signer signer = new MacSigner(verifierKey); + + private String signingKey = verifierKey; + + private SignatureVerifier verifier; + + /** + * @param tokenConverter the tokenConverter to set + */ + public void setAccessTokenConverter(AccessTokenConverter tokenConverter) { + this.tokenConverter = tokenConverter; + } + + /** + * @return the tokenConverter in use + */ + public AccessTokenConverter getAccessTokenConverter() { + return tokenConverter; + } + + /** + * @return the {@link JwtClaimsSetVerifier} used to verify the claim(s) in the JWT Claims Set + */ + public JwtClaimsSetVerifier getJwtClaimsSetVerifier() { + return this.jwtClaimsSetVerifier; + } + + /** + * @param jwtClaimsSetVerifier the {@link JwtClaimsSetVerifier} used to verify the claim(s) in the JWT Claims Set + */ + public void setJwtClaimsSetVerifier(JwtClaimsSetVerifier jwtClaimsSetVerifier) { + Assert.notNull(jwtClaimsSetVerifier, "jwtClaimsSetVerifier cannot be null"); + this.jwtClaimsSetVerifier = jwtClaimsSetVerifier; + } + + @Override + public Map convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { + return tokenConverter.convertAccessToken(token, authentication); + } + + @Override + public OAuth2AccessToken extractAccessToken(String value, Map map) { + return tokenConverter.extractAccessToken(value, map); + } + + @Override + public OAuth2Authentication extractAuthentication(Map map) { + return tokenConverter.extractAuthentication(map); + } + + /** + * Unconditionally set the verifier (the verifer key is then ignored). + * + * @param verifier the value to use + */ + public void setVerifier(SignatureVerifier verifier) { + this.verifier = verifier; + } + + /** + * Unconditionally set the signer to use (if needed). The signer key is then ignored. + * + * @param signer the value to use + */ + public void setSigner(Signer signer) { + this.signer = signer; + } + + /** + * Get the verification key for the token signatures. + * + * @return the key used to verify tokens + */ + public Map getKey() { + Map result = new LinkedHashMap(); + result.put("alg", signer.algorithm()); + result.put("value", verifierKey); + return result; + } + + public void setKeyPair(KeyPair keyPair) { + PrivateKey privateKey = keyPair.getPrivate(); + Assert.state(privateKey instanceof RSAPrivateKey, "KeyPair must be an RSA "); + signer = new RsaSigner((RSAPrivateKey) privateKey); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + verifier = new RsaVerifier(publicKey); + verifierKey = "-----BEGIN PUBLIC KEY-----\n" + new String(Base64.encode(publicKey.getEncoded())) + + "\n-----END PUBLIC KEY-----"; + } + + /** + * Sets the JWT signing key. It can be either a simple MAC key or an RSA key. RSA keys + * should be in OpenSSH format, as produced by ssh-keygen. + * + * @param key the key to be used for signing JWTs. + */ + public void setSigningKey(String key) { + Assert.hasText(key); + key = key.trim(); + + this.signingKey = key; + + if (isPublic(key)) { + signer = new RsaSigner(key); + logger.info("Configured with RSA signing key"); + } + else { + // Assume it's a MAC key + this.verifierKey = key; + signer = new MacSigner(key); + } + } + + /** + * @return true if the key has a public verifier + */ + private boolean isPublic(String key) { + return key.startsWith("-----BEGIN"); + } + + /** + * @return true if the signing key is a public key + */ + public boolean isPublic() { + return signer instanceof RsaSigner; + } + + /** + * The key used for verifying signatures produced by this class. This is not used but + * is returned from the endpoint to allow resource servers to obtain the key. + * + * For an HMAC key it will be the same value as the signing key and does not need to + * be set. For and RSA key, it should be set to the String representation of the + * public key, in a standard format (e.g. OpenSSH keys) + * + * @param key the signature verification key (typically an RSA public key) + */ + public void setVerifierKey(String key) { + this.verifierKey = key; + } + + public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { + DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken); + Map info = new LinkedHashMap(accessToken.getAdditionalInformation()); + String tokenId = result.getValue(); + if (!info.containsKey(TOKEN_ID)) { + info.put(TOKEN_ID, tokenId); + } + else { + tokenId = (String) info.get(TOKEN_ID); + } + result.setAdditionalInformation(info); + result.setValue(encode(result, authentication)); + OAuth2RefreshToken refreshToken = result.getRefreshToken(); + if (refreshToken != null) { + DefaultOAuth2AccessToken encodedRefreshToken = new DefaultOAuth2AccessToken(accessToken); + encodedRefreshToken.setValue(refreshToken.getValue()); + // Refresh tokens do not expire unless explicitly of the right type + encodedRefreshToken.setExpiration(null); + try { + Map claims = objectMapper + .parseMap(JwtHelper.decode(refreshToken.getValue()).getClaims()); + if (claims.containsKey(TOKEN_ID)) { + encodedRefreshToken.setValue(claims.get(TOKEN_ID).toString()); + } + } + catch (IllegalArgumentException e) { + } + Map refreshTokenInfo = new LinkedHashMap( + accessToken.getAdditionalInformation()); + refreshTokenInfo.put(TOKEN_ID, encodedRefreshToken.getValue()); + refreshTokenInfo.put(ACCESS_TOKEN_ID, tokenId); + encodedRefreshToken.setAdditionalInformation(refreshTokenInfo); + DefaultOAuth2RefreshToken token = new DefaultOAuth2RefreshToken( + encode(encodedRefreshToken, authentication)); + if (refreshToken instanceof ExpiringOAuth2RefreshToken) { + Date expiration = ((ExpiringOAuth2RefreshToken) refreshToken).getExpiration(); + encodedRefreshToken.setExpiration(expiration); + token = new DefaultExpiringOAuth2RefreshToken(encode(encodedRefreshToken, authentication), expiration); + } + result.setRefreshToken(token); + } + return result; + } + + public boolean isRefreshToken(OAuth2AccessToken token) { + return token.getAdditionalInformation().containsKey(ACCESS_TOKEN_ID); + } + + protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { + String content; + try { + content = objectMapper.formatMap(tokenConverter.convertAccessToken(accessToken, authentication)); + } + catch (Exception e) { + throw new IllegalStateException("Cannot convert access token to JSON", e); + } + String token = JwtHelper.encode(content, signer).getEncoded(); + return token; + } + + protected Map decode(String token) { + try { + Jwt jwt = JwtHelper.decodeAndVerify(token, verifier); + String claimsStr = jwt.getClaims(); + Map claims = objectMapper.parseMap(claimsStr); + if (claims.containsKey(EXP) && claims.get(EXP) instanceof Integer) { + Integer intValue = (Integer) claims.get(EXP); + claims.put(EXP, new Long(intValue)); + } + this.getJwtClaimsSetVerifier().verify(claims); + return claims; + } + catch (Exception e) { + throw new InvalidTokenException("Cannot convert access token to JSON", e); + } + } + + public void afterPropertiesSet() throws Exception { + if (verifier != null) { + // Assume signer also set independently if needed + return; + } + SignatureVerifier verifier = new MacSigner(verifierKey); + try { + verifier = new RsaVerifier(verifierKey); + } + catch (Exception e) { + logger.warn("Unable to create an RSA verifier from verifierKey (ignoreable if using MAC)"); + } + // Check the signing and verification keys match + if (signer instanceof RsaSigner) { + byte[] test = "test".getBytes(); + try { + verifier.verify(test, signer.sign(test)); + logger.info("Signing and verification RSA keys match"); + } + catch (InvalidSignatureException e) { + logger.error("Signing and verification RSA keys do not match"); + } + } + else if (verifier instanceof MacSigner) { + // Avoid a race condition where setters are called in the wrong order. Use of + // == is intentional. + Assert.state(this.signingKey == this.verifierKey, + "For MAC signing you do not need to specify the verifier key separately, and if you do it must match the signing key"); + } + this.verifier = verifier; + } + + private class NoOpJwtClaimsSetVerifier implements JwtClaimsSetVerifier { + @Override + public void verify(Map claims) throws InvalidTokenException { + } + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/JwtClaimsSetVerifier.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/JwtClaimsSetVerifier.java new file mode 100644 index 000000000..61d030ba9 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/JwtClaimsSetVerifier.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.token.store; + +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; + +import java.util.Map; + +/** + * This interface provides the capability of verifying the claim(s) + * contained in a JWT Claims Set, for example, expiration time (exp), + * not before (nbf), issuer (iss), audience (aud), subject (sub), etc. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Joe Grandja + * @since 2.2 + * @see JwtAccessTokenConverter + */ +@Deprecated +public interface JwtClaimsSetVerifier { + + /** + * Verify the claim(s) in the JWT Claims Set. + * + * @param claims the JWT Claims Set + * @throws InvalidTokenException if at least one claim failed verification + */ + void verify(Map claims) throws InvalidTokenException; + +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/JwtTokenStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/JwtTokenStore.java new file mode 100644 index 000000000..9e9c4b93e --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/JwtTokenStore.java @@ -0,0 +1,188 @@ +/* + * Copyright 2013-2014 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.provider.token.store; + +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken; +import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2RefreshToken; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.approval.Approval; +import org.springframework.security.oauth2.provider.approval.Approval.ApprovalStatus; +import org.springframework.security.oauth2.provider.approval.ApprovalStore; +import org.springframework.security.oauth2.provider.token.TokenStore; + +import java.util.*; + +/** + * A {@link TokenStore} implementation that just reads data from the tokens themselves. Not really a store since it + * never persists anything, and methods like {@link #getAccessToken(OAuth2Authentication)} always return null. But + * nevertheless a useful tool since it translates access tokens to and from authentications. Use this wherever a + * {@link TokenStore} is needed, but remember to use the same {@link JwtAccessTokenConverter} instance (or one with the same + * verifier) as was used when the tokens were minted. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class JwtTokenStore implements TokenStore { + + private JwtAccessTokenConverter jwtTokenEnhancer; + + private ApprovalStore approvalStore; + + /** + * Create a JwtTokenStore with this token enhancer (should be shared with the DefaultTokenServices if used). + * + * @param jwtTokenEnhancer + */ + public JwtTokenStore(JwtAccessTokenConverter jwtTokenEnhancer) { + this.jwtTokenEnhancer = jwtTokenEnhancer; + } + + /** + * ApprovalStore to be used to validate and restrict refresh tokens. + * + * @param approvalStore the approvalStore to set + */ + public void setApprovalStore(ApprovalStore approvalStore) { + this.approvalStore = approvalStore; + } + + @Override + public OAuth2Authentication readAuthentication(OAuth2AccessToken token) { + return readAuthentication(token.getValue()); + } + + @Override + public OAuth2Authentication readAuthentication(String token) { + return jwtTokenEnhancer.extractAuthentication(jwtTokenEnhancer.decode(token)); + } + + @Override + public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { + } + + @Override + public OAuth2AccessToken readAccessToken(String tokenValue) { + OAuth2AccessToken accessToken = convertAccessToken(tokenValue); + if (jwtTokenEnhancer.isRefreshToken(accessToken)) { + throw new InvalidTokenException("Encoded token is a refresh token"); + } + return accessToken; + } + + private OAuth2AccessToken convertAccessToken(String tokenValue) { + return jwtTokenEnhancer.extractAccessToken(tokenValue, jwtTokenEnhancer.decode(tokenValue)); + } + + @Override + public void removeAccessToken(OAuth2AccessToken token) { + // gh-807 Approvals (if any) should only be removed when Refresh Tokens are removed (or expired) + } + + @Override + public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) { + } + + @Override + public OAuth2RefreshToken readRefreshToken(String tokenValue) { + OAuth2AccessToken encodedRefreshToken = convertAccessToken(tokenValue); + OAuth2RefreshToken refreshToken = createRefreshToken(encodedRefreshToken); + if (approvalStore != null) { + OAuth2Authentication authentication = readAuthentication(tokenValue); + if (authentication.getUserAuthentication() != null) { + String userId = authentication.getUserAuthentication().getName(); + String clientId = authentication.getOAuth2Request().getClientId(); + Collection approvals = approvalStore.getApprovals(userId, clientId); + Collection approvedScopes = new HashSet(); + for (Approval approval : approvals) { + if (approval.isApproved()) { + approvedScopes.add(approval.getScope()); + } + } + if (!approvedScopes.containsAll(authentication.getOAuth2Request().getScope())) { + return null; + } + } + } + return refreshToken; + } + + private OAuth2RefreshToken createRefreshToken(OAuth2AccessToken encodedRefreshToken) { + if (!jwtTokenEnhancer.isRefreshToken(encodedRefreshToken)) { + throw new InvalidTokenException("Encoded token is not a refresh token"); + } + if (encodedRefreshToken.getExpiration()!=null) { + return new DefaultExpiringOAuth2RefreshToken(encodedRefreshToken.getValue(), + encodedRefreshToken.getExpiration()); + } + return new DefaultOAuth2RefreshToken(encodedRefreshToken.getValue()); + } + + @Override + public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) { + return readAuthentication(token.getValue()); + } + + @Override + public void removeRefreshToken(OAuth2RefreshToken token) { + remove(token.getValue()); + } + + @Override + public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { + // gh-807 Approvals (if any) should only be removed when Refresh Tokens are removed (or expired) + } + + @Override + public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { + // We don't want to accidentally issue a token, and we have no way to reconstruct the refresh token + return null; + } + + @Override + public Collection findTokensByClientIdAndUserName(String clientId, String userName) { + return Collections.emptySet(); + } + + @Override + public Collection findTokensByClientId(String clientId) { + return Collections.emptySet(); + } + + public void setTokenEnhancer(JwtAccessTokenConverter tokenEnhancer) { + this.jwtTokenEnhancer = tokenEnhancer; + } + + private void remove(String token) { + if (approvalStore != null) { + OAuth2Authentication auth = readAuthentication(token); + String clientId = auth.getOAuth2Request().getClientId(); + Authentication user = auth.getUserAuthentication(); + if (user != null) { + Collection approvals = new ArrayList(); + for (String scope : auth.getOAuth2Request().getScope()) { + approvals.add(new Approval(user.getName(), clientId, scope, new Date(), ApprovalStatus.APPROVED)); + } + approvalStore.revokeApprovals(approvals); + } + } + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/KeyStoreKeyFactory.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/KeyStoreKeyFactory.java new file mode 100644 index 000000000..2ca4aa271 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/KeyStoreKeyFactory.java @@ -0,0 +1,92 @@ +/* + * Copyright 2013-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.provider.token.store; + +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.PublicKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.spec.RSAPublicKeySpec; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.Resource; + +/** + * Factory for RSA key pairs from a JKS keystore file. User provides a {@link Resource} location of a keystore file and + * the password to unlock it, and the factory grabs the keypairs from the store by name (and optionally password). + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class KeyStoreKeyFactory { + + private static final Log logger = LogFactory.getLog(KeyStoreKeyFactory.class); + + private Resource resource; + + private char[] password; + + private KeyStore store; + + private Object lock = new Object(); + + public KeyStoreKeyFactory(Resource resource, char[] password) { + this.resource = resource; + this.password = password; + } + + public KeyPair getKeyPair(String alias) { + return getKeyPair(alias, password); + } + + public KeyPair getKeyPair(String alias, char[] password) { + InputStream inputStream = null; + try { + synchronized (lock) { + if (store == null) { + synchronized (lock) { + store = KeyStore.getInstance("jks"); + inputStream = resource.getInputStream(); + store.load(inputStream, this.password); + } + } + } + RSAPrivateCrtKey key = (RSAPrivateCrtKey) store.getKey(alias, password); + RSAPublicKeySpec spec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()); + PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(spec); + return new KeyPair(publicKey, key); + } + catch (Exception e) { + throw new IllegalStateException("Cannot load keys from store: " + resource, e); + } + finally { + try { + if (inputStream != null) { + inputStream.close(); + } + } + catch (IOException e) { + logger.warn("Cannot close open stream: ", e); + } + } + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/EllipticCurveJwkDefinition.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/EllipticCurveJwkDefinition.java new file mode 100644 index 000000000..360099d17 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/EllipticCurveJwkDefinition.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.token.store.jwk; + +/** + * A JSON Web Key (JWK) representation of an Elliptic Curve key. + * + * @author Michael Duergner + * @since 2.3 + * @see JSON Web Key (JWK) + * @see JSON Web Algorithms (JWA) + */ +final class EllipticCurveJwkDefinition extends JwkDefinition { + private final String x; + private final String y; + private final String curve; + + /** + * Creates an instance of an Elliptic Curve JSON Web Key (JWK). + * + * @param keyId the Key ID + * @param x5t the X.509 Certificate SHA-1 Thumbprint ("x5t") + * @param publicKeyUse the intended use of the Public Key + * @param algorithm the algorithm intended to be used + * @param x the x value to be used + * @param y the y value to be used + * @param curve the curve to be used + */ + EllipticCurveJwkDefinition(String keyId, + String x5t, + PublicKeyUse publicKeyUse, + CryptoAlgorithm algorithm, + String x, + String y, + String curve) { + super(keyId, x5t, KeyType.EC, publicKeyUse, algorithm); + this.x = x; + this.y = y; + this.curve = curve; + } + + String getX() { + return this.x; + } + + String getY() { + return this.y; + } + + String getCurve() { + return this.curve; + } + + /** + * The supported Named Curves. + */ + enum NamedCurve { + P256("P-256"), + P384("P-384"), + P521("P-521"); + + private final String value; + + NamedCurve(String value) { + this.value = value; + } + + String value() { + return this.value; + } + + static EllipticCurveJwkDefinition.NamedCurve fromValue(String curveName) { + EllipticCurveJwkDefinition.NamedCurve result = null; + for (EllipticCurveJwkDefinition.NamedCurve namedCurve : values()) { + if (namedCurve.value().equals(curveName)) { + result = namedCurve; + break; + } + } + return result; + } + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java new file mode 100644 index 000000000..15c3f1c2c --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.token.store.jwk; + +/** + * Shared attribute values used by {@link JwkTokenStore} and associated collaborators. + * + * @author Joe Grandja + * @author Michael Duergner + */ +final class JwkAttributes { + + /** + * The "kid" (key ID) parameter used in a JWT header and in a JWK. + */ + static final String KEY_ID = "kid"; + + /** + * The "x5t" (X.509 Certificate SHA-1 Thumbprint) parameter used in a JWT header and in a JWK. + */ + static final String X5T = "x5t"; + + /** + * The "kty" (key type) parameter identifies the cryptographic algorithm family + * used by a JWK, for example, "RSA" or "EC". + */ + static final String KEY_TYPE = "kty"; + + /** + * The "alg" (algorithm) parameter used in a JWT header and in a JWK. + */ + static final String ALGORITHM = "alg"; + + /** + * The "use" (public key use) parameter identifies the intended use of the public key. + * For example, whether a public key is used for encrypting data or verifying the signature on data. + */ + static final String PUBLIC_KEY_USE = "use"; + + /** + * The "n" (modulus) parameter contains the modulus value for a RSA public key. + */ + static final String RSA_PUBLIC_KEY_MODULUS = "n"; + + /** + * The "e" (exponent) parameter contains the exponent value for a RSA public key. + */ + static final String RSA_PUBLIC_KEY_EXPONENT = "e"; + + /** + * The "x" parameter contains the x value for a EC public key. + */ + static final String EC_PUBLIC_KEY_X = "x"; + + /** + * The "y" parameter contains the y value for a EC public key. + */ + static final String EC_PUBLIC_KEY_Y = "y"; + + /** + * The "crv" (curve) parameter contains the curve value for a EC public key. + */ + static final String EC_PUBLIC_KEY_CURVE = "crv"; + + /** + * A JWK Set is a JSON object that has a "keys" member + * and its value is an array (set) of JWKs. + */ + static final String KEYS = "keys"; +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java new file mode 100644 index 000000000..f8392fa10 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java @@ -0,0 +1,218 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.token.store.jwk; + +/** + * The base representation of a JSON Web Key (JWK). + * + * @see JSON Web Key (JWK) + * + * @author Joe Grandja + * @author Michael Duergner + */ +abstract class JwkDefinition { + private final String keyId; + private final String x5t; + private final KeyType keyType; + private final PublicKeyUse publicKeyUse; + private final CryptoAlgorithm algorithm; + + /** + * Creates an instance with the common attributes of a JWK. + * + * @param keyId the Key ID + * @param x5t the X.509 Certificate SHA-1 Thumbprint ("x5t") + * @param keyType the Key Type + * @param publicKeyUse the intended use of the Public Key + * @param algorithm the algorithm intended to be used + */ + protected JwkDefinition(String keyId, + String x5t, + KeyType keyType, + PublicKeyUse publicKeyUse, + CryptoAlgorithm algorithm) { + this.keyId = keyId; + this.x5t = x5t; + this.keyType = keyType; + this.publicKeyUse = publicKeyUse; + this.algorithm = algorithm; + } + + /** + * @return the Key ID ("kid") + */ + String getKeyId() { + return this.keyId; + } + + /** + * @return the X.509 Certificate SHA-1 Thumbprint ("x5t") + */ + String getX5t() { + return this.x5t; + } + + /** + * @return the Key Type ("kty") + */ + KeyType getKeyType() { + return this.keyType; + } + + /** + * @return the intended use of the Public Key ("use") + */ + PublicKeyUse getPublicKeyUse() { + return this.publicKeyUse; + } + + /** + * + * @return the algorithm intended to be used ("alg") + */ + CryptoAlgorithm getAlgorithm() { + return this.algorithm; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + + JwkDefinition that = (JwkDefinition) obj; + if (!this.getKeyId().equals(that.getKeyId())) { + return false; + } + if (this.getX5t() == null) { + if (that.getX5t() != null) + return false; + } + else if (!this.getX5t().equals(that.getX5t())) + return false; + + return this.getKeyType().equals(that.getKeyType()); + } + + @Override + public int hashCode() { + int result = this.getKeyId().hashCode(); + result = 31 * result + this.getKeyType().hashCode(); + result = 31 * result + ((this.getX5t() == null) ? 0 : this.getX5t().hashCode()); + return result; + } + + /** + * The defined Key Type ("kty") values. + */ + enum KeyType { + RSA("RSA"), + EC("EC"), + OCT("oct"); + + private final String value; + + KeyType(String value) { + this.value = value; + } + + String value() { + return this.value; + } + + static KeyType fromValue(String value) { + KeyType result = null; + for (KeyType keyType : values()) { + if (keyType.value().equals(value)) { + result = keyType; + break; + } + } + return result; + } + } + + /** + * The defined Public Key Use ("use") values. + */ + enum PublicKeyUse { + SIG("sig"), + ENC("enc"); + + private final String value; + + PublicKeyUse(String value) { + this.value = value; + } + + String value() { + return this.value; + } + + static PublicKeyUse fromValue(String value) { + PublicKeyUse result = null; + for (PublicKeyUse publicKeyUse : values()) { + if (publicKeyUse.value().equals(value)) { + result = publicKeyUse; + break; + } + } + return result; + } + } + + /** + * The defined Algorithm ("alg") values. + */ + enum CryptoAlgorithm { + RS256("SHA256withRSA", "RS256"), + RS384("SHA384withRSA", "RS384"), + RS512("SHA512withRSA", "RS512"), + ES256("SHA256withECDSA", "ES256"), + ES384("SHA384withECDSA", "ES384"), + ES512("SHA512withECDSA", "ES512"); + + private final String standardName; // JCA Standard Name + private final String headerParamValue; + + CryptoAlgorithm(String standardName, String headerParamValue) { + this.standardName = standardName; + this.headerParamValue = headerParamValue; + } + + String standardName() { + return this.standardName; + } + + String headerParamValue() { + return this.headerParamValue; + } + + static CryptoAlgorithm fromHeaderParamValue(String headerParamValue) { + CryptoAlgorithm result = null; + for (CryptoAlgorithm algorithm : values()) { + if (algorithm.headerParamValue().equals(headerParamValue)) { + result = algorithm; + break; + } + } + return result; + } + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java new file mode 100644 index 000000000..8340106e3 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java @@ -0,0 +1,234 @@ +/* + * Copyright 2012-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.provider.token.store.jwk; + +import org.springframework.security.jwt.codec.Codecs; +import org.springframework.security.jwt.crypto.sign.EllipticCurveVerifier; +import org.springframework.security.jwt.crypto.sign.RsaVerifier; +import org.springframework.security.jwt.crypto.sign.SignatureVerifier; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.KeyFactory; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.RSAPublicKeySpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A source for JSON Web Key(s) (JWK) that is solely responsible for fetching (and caching) + * the JWK Set (a set of JWKs) from the URL supplied to the constructor. + * + * @see JwkSetConverter + * @see JwkDefinition + * @see SignatureVerifier + * @see JWK Set Format + * + * @author Joe Grandja + * @author Michael Duergner + * @author Bjoern Eickvonder + */ +class JwkDefinitionSource { + private final List jwkSetUrls; + private final Map jwkDefinitions = new ConcurrentHashMap(); + private static final JwkSetConverter jwkSetConverter = new JwkSetConverter(); + + /** + * Creates a new instance using the provided URL as the location for the JWK Set. + * + * @param jwkSetUrl the JWK Set URL + */ + JwkDefinitionSource(String jwkSetUrl) { + this(Arrays.asList(jwkSetUrl)); + } + + /** + * Creates a new instance using the provided URLs as the location for the JWK Sets. + * + * @param jwkSetUrls the JWK Set URLs + */ + JwkDefinitionSource(List jwkSetUrls) { + this.jwkSetUrls = new ArrayList(); + for(String jwkSetUrl : jwkSetUrls) { + try { + this.jwkSetUrls.add(new URL(jwkSetUrl)); + } catch (MalformedURLException ex) { + throw new IllegalArgumentException("Invalid JWK Set URL: " + ex.getMessage(), ex); + } + } + } + + /** + * Returns the JWK definition matching the provided keyId ("kid") or provided thumbprint ("x5t"). + * If the JWK definition is not available in the internal cache then {@link #loadJwkDefinitions(URL)} + * will be called (to re-load the cache) and then followed-up with a second attempt to locate the JWK definition. + * + * @param keyId the Key ID ("kid"), if not given x5t will be checked + * @param x5t the X.509 Certificate SHA-1 Thumbprint ("x5t"), will only be checked if keyId is not given + * @return the matching {@link JwkDefinition} or null if not found + */ + JwkDefinitionHolder getDefinitionLoadIfNecessary(String keyId, String x5t) { + JwkDefinitionHolder result = this.getDefinition(keyId, x5t); + if (result != null) { + return result; + } + synchronized (this.jwkDefinitions) { + result = this.getDefinition(keyId, x5t); + if (result != null) { + return result; + } + Map newJwkDefinitions = new LinkedHashMap(); + for (URL jwkSetUrl : jwkSetUrls) { + newJwkDefinitions.putAll(loadJwkDefinitions(jwkSetUrl)); + } + this.jwkDefinitions.clear(); + this.jwkDefinitions.putAll(newJwkDefinitions); + return this.getDefinition(keyId, x5t); + } + } + + /** + * Returns the JWK definition matching the provided keyId ("kid"). + * + * @param keyId the Key ID ("kid"), if not given x5t will be checked + * @param x5t the X.509 Certificate SHA-1 Thumbprint ("x5t"), will only be checked if keyId is not given + * @return the matching {@link JwkDefinition} or null if not found + */ + private JwkDefinitionHolder getDefinition(String keyId, String x5t) { + JwkDefinitionHolder result = null; + if (keyId != null) { + result = this.jwkDefinitions.get(keyId); + } else if (x5t != null) { + Iterator iter = this.jwkDefinitions.values().iterator(); + while (result == null && iter.hasNext()) { + JwkDefinitionHolder entry = iter.next(); + if (x5t.equals(entry.getJwkDefinition().getX5t())) { + result = entry; + } + } + } + return result; + } + + /** + * Fetches the JWK Set from the provided URL and + * returns a Map keyed by the JWK keyId ("kid") + * and mapped to an association of the {@link JwkDefinition} and {@link SignatureVerifier}. + * Uses a {@link JwkSetConverter} to convert the JWK Set URL source to a set of {@link JwkDefinition}(s) + * followed by the instantiation of a {@link SignatureVerifier} which is associated to it's {@link JwkDefinition}. + * + * @param jwkSetUrl the JWK Set URL + * @return a Map keyed by the JWK keyId and mapped to an association of {@link JwkDefinition} and {@link SignatureVerifier} + * @see JwkSetConverter + */ + static Map loadJwkDefinitions(URL jwkSetUrl) { + InputStream jwkSetSource; + try { + jwkSetSource = jwkSetUrl.openStream(); + } catch (IOException ex) { + throw new JwkException("An I/O error occurred while reading from the JWK Set source: " + ex.getMessage(), ex); + } + + Set jwkDefinitionSet = jwkSetConverter.convert(jwkSetSource); + + Map jwkDefinitions = new LinkedHashMap(); + + for (JwkDefinition jwkDefinition : jwkDefinitionSet) { + if (JwkDefinition.KeyType.RSA.equals(jwkDefinition.getKeyType())) { + jwkDefinitions.put(jwkDefinition.getKeyId(), + new JwkDefinitionHolder(jwkDefinition, createRsaVerifier((RsaJwkDefinition) jwkDefinition))); + } else if (JwkDefinition.KeyType.EC.equals(jwkDefinition.getKeyType())) { + jwkDefinitions.put(jwkDefinition.getKeyId(), + new JwkDefinitionHolder(jwkDefinition, createEcVerifier((EllipticCurveJwkDefinition) jwkDefinition))); + } + } + + return jwkDefinitions; + } + + private static RsaVerifier createRsaVerifier(RsaJwkDefinition rsaDefinition) { + RsaVerifier result; + try { + BigInteger modulus = new BigInteger(1, Codecs.b64UrlDecode(rsaDefinition.getModulus())); + BigInteger exponent = new BigInteger(1, Codecs.b64UrlDecode(rsaDefinition.getExponent())); + + RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance("RSA") + .generatePublic(new RSAPublicKeySpec(modulus, exponent)); + + if (rsaDefinition.getAlgorithm() != null) { + result = new RsaVerifier(rsaPublicKey, rsaDefinition.getAlgorithm().standardName()); + } else { + result = new RsaVerifier(rsaPublicKey); + } + + } catch (Exception ex) { + throw new JwkException("An error occurred while creating a RSA Public Key Verifier for " + + rsaDefinition.getKeyId() + " : " + ex.getMessage(), ex); + } + return result; + } + + private static EllipticCurveVerifier createEcVerifier(EllipticCurveJwkDefinition ecDefinition) { + EllipticCurveVerifier result; + try { + BigInteger x = new BigInteger(1, Codecs.b64UrlDecode(ecDefinition.getX())); + BigInteger y = new BigInteger(1, Codecs.b64UrlDecode(ecDefinition.getY())); + + String algorithm = null; + if (EllipticCurveJwkDefinition.NamedCurve.P256.value().equals(ecDefinition.getCurve())) { + algorithm = JwkDefinition.CryptoAlgorithm.ES256.standardName(); + } else if (EllipticCurveJwkDefinition.NamedCurve.P384.value().equals(ecDefinition.getCurve())) { + algorithm = JwkDefinition.CryptoAlgorithm.ES384.standardName(); + } else if (EllipticCurveJwkDefinition.NamedCurve.P521.value().equals(ecDefinition.getCurve())) { + algorithm = JwkDefinition.CryptoAlgorithm.ES512.standardName(); + } + + result = new EllipticCurveVerifier(x, y, ecDefinition.getCurve(), algorithm); + + } catch (Exception ex) { + throw new JwkException("An error occurred while creating an EC Public Key Verifier for " + + ecDefinition.getKeyId() + " : " + ex.getMessage(), ex); + } + return result; + } + + static class JwkDefinitionHolder { + private final JwkDefinition jwkDefinition; + private final SignatureVerifier signatureVerifier; + + private JwkDefinitionHolder(JwkDefinition jwkDefinition, SignatureVerifier signatureVerifier) { + this.jwkDefinition = jwkDefinition; + this.signatureVerifier = signatureVerifier; + } + + JwkDefinition getJwkDefinition() { + return jwkDefinition; + } + + SignatureVerifier getSignatureVerifier() { + return signatureVerifier; + } + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java new file mode 100644 index 000000000..36d56615c --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.token.store.jwk; + +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; + +/** + * General exception for JSON Web Key (JWK) related errors. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Joe Grandja + */ +@Deprecated +public class JwkException extends OAuth2Exception { + private static final String SERVER_ERROR_ERROR_CODE = "server_error"; + private String errorCode = SERVER_ERROR_ERROR_CODE; + private int httpStatus = 500; + + public JwkException(String message) { + super(message); + } + + public JwkException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Returns the error used in the OAuth2 Error Response + * sent back to the caller. The default is "server_error". + * + * @return the error used in the OAuth2 Error Response + */ + @Override + public String getOAuth2ErrorCode() { + return this.errorCode; + } + + /** + * Returns the Http Status used in the OAuth2 Error Response + * sent back to the caller. The default is 500. + * + * @return the Http Status set on the OAuth2 Error Response + */ + @Override + public int getHttpErrorCode() { + return this.httpStatus; + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java new file mode 100644 index 000000000..930884c12 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java @@ -0,0 +1,237 @@ +/* + * Copyright 2012-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.provider.token.store.jwk; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import org.springframework.core.convert.converter.Converter; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.*; + +/** + * A {@link Converter} that converts the supplied InputStream to a Set of {@link JwkDefinition}(s). + * The source of the InputStream must be a JWK Set representation which is a JSON object + * that has a "keys" member and its value is an array of JWKs. + *
+ *
+ * + * NOTE: The Key Type ("kty") currently supported by this {@link Converter} is {@link JwkDefinition.KeyType#RSA}. + *
+ *
+ * + * @see JwkDefinition + * @see JWK Set Format + * + * @author Joe Grandja + * @author Vedran Pavic + * @author Michael Duergner + */ +class JwkSetConverter implements Converter> { + private final JsonFactory factory = new JsonFactory(); + + /** + * Converts the supplied InputStream to a Set of {@link JwkDefinition}(s). + * + * @param jwkSetSource the source for the JWK Set + * @return a Set of {@link JwkDefinition}(s) + * @throws JwkException if the JWK Set JSON object is invalid + */ + @Override + public Set convert(InputStream jwkSetSource) { + Set jwkDefinitions; + JsonParser parser = null; + + try { + parser = this.factory.createParser(jwkSetSource); + + if (parser.nextToken() != JsonToken.START_OBJECT) { + throw new JwkException("Invalid JWK Set Object."); + } + if (parser.nextToken() != JsonToken.FIELD_NAME) { + throw new JwkException("Invalid JWK Set Object."); + } + if (!parser.getCurrentName().equals(KEYS)) { + throw new JwkException("Invalid JWK Set Object. The JWK Set MUST have a " + KEYS + " attribute."); + } + if (parser.nextToken() != JsonToken.START_ARRAY) { + throw new JwkException("Invalid JWK Set Object. The JWK Set MUST have an array of JWK(s)."); + } + + jwkDefinitions = new LinkedHashSet(); + Map attributes = new HashMap(); + + while (parser.nextToken() == JsonToken.START_OBJECT) { + attributes.clear(); + while (parser.nextToken() == JsonToken.FIELD_NAME) { + String attributeName = parser.getCurrentName(); + // gh-1082 - skip arrays such as x5c as we can't deal with them yet + if (parser.nextToken() == JsonToken.START_ARRAY) { + while (parser.nextToken() != JsonToken.END_ARRAY) { + } + } else { + attributes.put(attributeName, parser.getValueAsString()); + } + } + + // gh-1871 - only accept public key use (sig) + JwkDefinition.PublicKeyUse publicKeyUse = + JwkDefinition.PublicKeyUse.fromValue(attributes.get(PUBLIC_KEY_USE)); + if (!JwkDefinition.PublicKeyUse.SIG.equals(publicKeyUse)) { + continue; + } + + JwkDefinition jwkDefinition = null; + JwkDefinition.KeyType keyType = + JwkDefinition.KeyType.fromValue(attributes.get(KEY_TYPE)); + if (JwkDefinition.KeyType.RSA.equals(keyType)) { + jwkDefinition = this.createRsaJwkDefinition(attributes); + } else if (JwkDefinition.KeyType.EC.equals(keyType)) { + jwkDefinition = this.createEllipticCurveJwkDefinition(attributes); + } + if (jwkDefinition != null) { + if (!jwkDefinitions.add(jwkDefinition)) { + throw new JwkException("Duplicate JWK found in Set: " + + jwkDefinition.getKeyId() + " (" + KEY_ID + ")"); + } + } + } + + } catch (IOException ex) { + throw new JwkException("An I/O error occurred while reading the JWK Set: " + ex.getMessage(), ex); + } finally { + try { + if (parser != null) parser.close(); + } catch (IOException ex) { } + } + + return jwkDefinitions; + } + + /** + * Creates a {@link RsaJwkDefinition} based on the supplied attributes. + * + * @param attributes the attributes used to create the {@link RsaJwkDefinition} + * @return a {@link JwkDefinition} representation of a RSA Key + * @throws JwkException if at least one attribute value is missing or invalid for a RSA Key + */ + private JwkDefinition createRsaJwkDefinition(Map attributes) { + // kid + String keyId = attributes.get(KEY_ID); + if (!StringUtils.hasText(keyId)) { + throw new JwkException(KEY_ID + " is a required attribute for a JWK."); + } + String x5t = attributes.get(X5T); + + // use + JwkDefinition.PublicKeyUse publicKeyUse = + JwkDefinition.PublicKeyUse.fromValue(attributes.get(PUBLIC_KEY_USE)); + if (!JwkDefinition.PublicKeyUse.SIG.equals(publicKeyUse)) { + return null; + } + + // alg + JwkDefinition.CryptoAlgorithm algorithm = + JwkDefinition.CryptoAlgorithm.fromHeaderParamValue(attributes.get(ALGORITHM)); + if (algorithm != null && + !JwkDefinition.CryptoAlgorithm.RS256.equals(algorithm) && + !JwkDefinition.CryptoAlgorithm.RS384.equals(algorithm) && + !JwkDefinition.CryptoAlgorithm.RS512.equals(algorithm)) { + throw new JwkException(algorithm.standardName() + " (" + ALGORITHM + ") is currently not supported."); + } + + // n + String modulus = attributes.get(RSA_PUBLIC_KEY_MODULUS); + if (!StringUtils.hasText(modulus)) { + throw new JwkException(RSA_PUBLIC_KEY_MODULUS + " is a required attribute for a RSA JWK."); + } + + // e + String exponent = attributes.get(RSA_PUBLIC_KEY_EXPONENT); + if (!StringUtils.hasText(exponent)) { + throw new JwkException(RSA_PUBLIC_KEY_EXPONENT + " is a required attribute for a RSA JWK."); + } + + RsaJwkDefinition jwkDefinition = new RsaJwkDefinition( + keyId, x5t, publicKeyUse, algorithm, modulus, exponent); + + return jwkDefinition; + } + + /** + * Creates an {@link EllipticCurveJwkDefinition} based on the supplied attributes. + * + * @param attributes the attributes used to create the {@link EllipticCurveJwkDefinition} + * @return a {@link JwkDefinition} representation of an EC Key + * @throws JwkException if at least one attribute value is missing or invalid for an EC Key + */ + private JwkDefinition createEllipticCurveJwkDefinition(Map attributes) { + // kid + String keyId = attributes.get(KEY_ID); + if (!StringUtils.hasText(keyId)) { + throw new JwkException(KEY_ID + " is a required attribute for an EC JWK."); + } + String x5t = attributes.get(X5T); + + // use + JwkDefinition.PublicKeyUse publicKeyUse = + JwkDefinition.PublicKeyUse.fromValue(attributes.get(PUBLIC_KEY_USE)); + if (!JwkDefinition.PublicKeyUse.SIG.equals(publicKeyUse)) { + return null; + } + + // alg + JwkDefinition.CryptoAlgorithm algorithm = + JwkDefinition.CryptoAlgorithm.fromHeaderParamValue(attributes.get(ALGORITHM)); + if (algorithm != null && + !JwkDefinition.CryptoAlgorithm.ES256.equals(algorithm) && + !JwkDefinition.CryptoAlgorithm.ES384.equals(algorithm) && + !JwkDefinition.CryptoAlgorithm.ES512.equals(algorithm)) { + throw new JwkException(algorithm.standardName() + " (" + ALGORITHM + ") is currently not supported."); + } + + // x + String x = attributes.get(EC_PUBLIC_KEY_X); + if (!StringUtils.hasText(x)) { + throw new JwkException(EC_PUBLIC_KEY_X + " is a required attribute for an EC JWK."); + } + + // y + String y = attributes.get(EC_PUBLIC_KEY_Y); + if (!StringUtils.hasText(y)) { + throw new JwkException(EC_PUBLIC_KEY_Y + " is a required attribute for an EC JWK."); + } + + // crv + String curve = attributes.get(EC_PUBLIC_KEY_CURVE); + if (!StringUtils.hasText(curve)) { + throw new JwkException(EC_PUBLIC_KEY_CURVE + " is a required attribute for an EC JWK."); + } + + EllipticCurveJwkDefinition jwkDefinition = new EllipticCurveJwkDefinition( + keyId, x5t, publicKeyUse, algorithm, x, y, curve); + + return jwkDefinition; + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java new file mode 100644 index 000000000..fab93d130 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java @@ -0,0 +1,320 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.token.store.jwk; + +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2RefreshToken; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.AccessTokenConverter; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.JwtClaimsSetVerifier; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * A {@link TokenStore} implementation that provides support for verifying the + * JSON Web Signature (JWS) for a JSON Web Token (JWT) using a JSON Web Key (JWK). + *
+ *
+ * + * This {@link TokenStore} implementation is exclusively meant to be used by a Resource Server as + * it's sole responsibility is to decode a JWT and verify it's signature (JWS) using the corresponding JWK. + *
+ *
+ * + * NOTE: + * There are a few operations defined by {@link TokenStore} that are not applicable for a Resource Server. + * In these cases, the method implementation will explicitly throw a + * {@link JwkException} reporting "This operation is not supported". + *
+ *
+ * + * The unsupported operations are as follows: + *

    + *
  • {@link #storeAccessToken(OAuth2AccessToken, OAuth2Authentication)}
  • + *
  • {@link #removeAccessToken(OAuth2AccessToken)}
  • + *
  • {@link #storeRefreshToken(OAuth2RefreshToken, OAuth2Authentication)}
  • + *
  • {@link #readRefreshToken(String)}
  • + *
  • {@link #readAuthenticationForRefreshToken(OAuth2RefreshToken)}
  • + *
  • {@link #removeRefreshToken(OAuth2RefreshToken)}
  • + *
  • {@link #removeAccessTokenUsingRefreshToken(OAuth2RefreshToken)}
  • + *
  • {@link #getAccessToken(OAuth2Authentication)}
  • + *
  • {@link #findTokensByClientIdAndUserName(String, String)}
  • + *
  • {@link #findTokensByClientId(String)}
  • + *
+ *
+ * + * This implementation delegates to an internal instance of a {@link JwtTokenStore} which uses a + * specialized extension of {@link JwtAccessTokenConverter}. + * This specialized {@link JwtAccessTokenConverter} is capable of fetching (and caching) + * the JWK Set (a set of JWKs) from the URL supplied to the constructor of this implementation. + *
+ *
+ * + * The {@link JwtAccessTokenConverter} will verify the JWS in the following step sequence: + *
+ *
+ *
    + *
  1. Extract the "kid" parameter from the JWT header.
  2. + *
  3. Find the matching JWK with the corresponding "kid" attribute.
  4. + *
  5. Obtain the SignatureVerifier associated with the JWK and verify the signature.
  6. + *
+ *
+ * NOTE: The algorithms currently supported by this implementation are: RS256, RS384 and RS512. + *
+ *
+ * + * @see JwtTokenStore + * @see JSON Web Key (JWK) + * @see JSON Web Token (JWT) + * @see JSON Web Signature (JWS) + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Joe Grandja + */ +@Deprecated +public final class JwkTokenStore implements TokenStore { + private final TokenStore delegate; + + /** + * Creates a new instance using the provided URL as the location for the JWK Set. + * + * @param jwkSetUrl the JWK Set URL + */ + public JwkTokenStore(String jwkSetUrl) { + this(Arrays.asList(jwkSetUrl)); + } + + /** + * Creates a new instance using the provided URLs as the location for the JWK Sets. + * + * @param jwkSetUrls the JWK Set URLs + */ + public JwkTokenStore(List jwkSetUrls) { + this(jwkSetUrls, null, null); + } + + /** + * Creates a new instance using the provided URL as the location for the JWK Set + * and a custom {@link AccessTokenConverter}. + * + * @param jwkSetUrl the JWK Set URL + * @param accessTokenConverter a custom {@link AccessTokenConverter} + */ + public JwkTokenStore(String jwkSetUrl, AccessTokenConverter accessTokenConverter) { + this(jwkSetUrl, accessTokenConverter, null); + } + + /** + * Creates a new instance using the provided URL as the location for the JWK Set + * and a custom {@link JwtClaimsSetVerifier}. + * + * @param jwkSetUrl the JWK Set URL + * @param jwtClaimsSetVerifier a custom {@link JwtClaimsSetVerifier} + */ + public JwkTokenStore(String jwkSetUrl, JwtClaimsSetVerifier jwtClaimsSetVerifier) { + this(jwkSetUrl, null, jwtClaimsSetVerifier); + } + + /** + * Creates a new instance using the provided URL as the location for the JWK Set + * and a custom {@link AccessTokenConverter} and {@link JwtClaimsSetVerifier}. + * + * @param jwkSetUrl the JWK Set URL + * @param accessTokenConverter a custom {@link AccessTokenConverter} + * @param jwtClaimsSetVerifier a custom {@link JwtClaimsSetVerifier} + */ + public JwkTokenStore(String jwkSetUrl, AccessTokenConverter accessTokenConverter, + JwtClaimsSetVerifier jwtClaimsSetVerifier) { + + this(Arrays.asList(jwkSetUrl), accessTokenConverter, jwtClaimsSetVerifier); + } + + /** + * Creates a new instance using the provided URLs as the location for the JWK Sets + * and a custom {@link AccessTokenConverter} and {@link JwtClaimsSetVerifier}. + * + * @param jwkSetUrls the JWK Set URLs + * @param accessTokenConverter a custom {@link AccessTokenConverter} + * @param jwtClaimsSetVerifier a custom {@link JwtClaimsSetVerifier} + */ + public JwkTokenStore(List jwkSetUrls, AccessTokenConverter accessTokenConverter, + JwtClaimsSetVerifier jwtClaimsSetVerifier) { + + JwkDefinitionSource jwkDefinitionSource = new JwkDefinitionSource(jwkSetUrls); + JwkVerifyingJwtAccessTokenConverter jwtVerifyingAccessTokenConverter = + new JwkVerifyingJwtAccessTokenConverter(jwkDefinitionSource); + if (accessTokenConverter != null) { + jwtVerifyingAccessTokenConverter.setAccessTokenConverter(accessTokenConverter); + } + if (jwtClaimsSetVerifier != null) { + jwtVerifyingAccessTokenConverter.setJwtClaimsSetVerifier(jwtClaimsSetVerifier); + } + this.delegate = new JwtTokenStore(jwtVerifyingAccessTokenConverter); + } + + /** + * Delegates to the internal instance {@link JwtTokenStore#readAuthentication(OAuth2AccessToken)}. + * + * @param token the access token + * @return the {@link OAuth2Authentication} representation of the access token + */ + @Override + public OAuth2Authentication readAuthentication(OAuth2AccessToken token) { + return this.delegate.readAuthentication(token); + } + + /** + * Delegates to the internal instance {@link JwtTokenStore#readAuthentication(String)}. + * + * @param tokenValue the access token value + * @return the {@link OAuth2Authentication} representation of the access token + */ + @Override + public OAuth2Authentication readAuthentication(String tokenValue) { + return this.delegate.readAuthentication(tokenValue); + } + + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ + @Override + public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { + throw this.operationNotSupported(); + } + + /** + * Delegates to the internal instance {@link JwtTokenStore#readAccessToken(String)}. + * + * @param tokenValue the access token value + * @return the {@link OAuth2AccessToken} representation of the access token value + */ + @Override + public OAuth2AccessToken readAccessToken(String tokenValue) { + return this.delegate.readAccessToken(tokenValue); + } + + /** + * Delegates to the internal instance {@link JwtTokenStore#removeAccessToken(OAuth2AccessToken)}. + * + * @param token the access token + */ + @Override + public void removeAccessToken(OAuth2AccessToken token) { + this.delegate.removeAccessToken(token); + } + + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ + @Override + public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) { + throw this.operationNotSupported(); + } + + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ + @Override + public OAuth2RefreshToken readRefreshToken(String tokenValue) { + throw this.operationNotSupported(); + } + + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ + @Override + public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) { + throw this.operationNotSupported(); + } + + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ + @Override + public void removeRefreshToken(OAuth2RefreshToken token) { + throw this.operationNotSupported(); + } + + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ + @Override + public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { + throw this.operationNotSupported(); + } + + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ + @Override + public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { + throw this.operationNotSupported(); + } + + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ + @Override + public Collection findTokensByClientIdAndUserName(String clientId, String userName) { + throw this.operationNotSupported(); + } + + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ + @Override + public Collection findTokensByClientId(String clientId) { + throw this.operationNotSupported(); + } + + private JwkException operationNotSupported() { + return new JwkException("This operation is not supported."); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java new file mode 100644 index 000000000..58f166cfc --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java @@ -0,0 +1,152 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.token.store.jwk; + +import org.springframework.security.jwt.Jwt; +import org.springframework.security.jwt.JwtHelper; +import org.springframework.security.jwt.crypto.sign.SignatureVerifier; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.common.util.JsonParser; +import org.springframework.security.oauth2.common.util.JsonParserFactory; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; + +import java.util.Map; + +import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.ALGORITHM; +import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.KEY_ID; +import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.X5T; + +/** + * A specialized extension of {@link JwtAccessTokenConverter} that is responsible for verifying + * the JSON Web Signature (JWS) for a JSON Web Token (JWT) using the corresponding JSON Web Key (JWK). + * This implementation is associated with a {@link JwkDefinitionSource} for looking up + * the matching {@link JwkDefinition} using the value of the JWT header parameter "kid". + *
+ *
+ * + * The JWS is verified in the following step sequence: + *
+ *
+ *

    + *
  1. Extract the "kid" and "x5t" parameters from the JWT header.
  2. + *
  3. Find the matching {@link JwkDefinition} from the {@link JwkDefinitionSource} with the corresponding "kid" or "x5t" attribute.
  4. + *
  5. Obtain the {@link SignatureVerifier} associated with the {@link JwkDefinition} via the {@link JwkDefinitionSource} and verify the signature.
  6. + *
+ *
+ * NOTE: The algorithms currently supported by this implementation are: RS256, RS384 and RS512. + *
+ *
+ * + * NOTE: This {@link JwtAccessTokenConverter} does not support signing JWTs (JWS) and therefore + * the {@link #encode(OAuth2AccessToken, OAuth2Authentication)} method implementation, if called, + * will explicitly throw a {@link JwkException} reporting "JWT signing (JWS) is not supported.". + *
+ *
+ * + * @see JwtAccessTokenConverter + * @see JwtHeaderConverter + * @see JwkDefinitionSource + * @see JwkDefinition + * @see SignatureVerifier + * @see JSON Web Key (JWK) + * @see JSON Web Token (JWT) + * @see JSON Web Signature (JWS) + * + * @author Joe Grandja + * @author bjoern Eickvonder + */ +class JwkVerifyingJwtAccessTokenConverter extends JwtAccessTokenConverter { + private final JwkDefinitionSource jwkDefinitionSource; + private final JwtHeaderConverter jwtHeaderConverter = new JwtHeaderConverter(); + private final JsonParser jsonParser = JsonParserFactory.create(); + + /** + * Creates a new instance using the provided {@link JwkDefinitionSource} + * as the primary source for looking up {@link JwkDefinition}(s). + * + * @param jwkDefinitionSource the source for {@link JwkDefinition}(s) + */ + JwkVerifyingJwtAccessTokenConverter(JwkDefinitionSource jwkDefinitionSource) { + this.jwkDefinitionSource = jwkDefinitionSource; + } + + /** + * Decodes and validates the supplied JWT followed by signature verification + * before returning the Claims from the JWT Payload. + * + * @param token the JSON Web Token + * @return a Map of the JWT Claims + * @throws JwkException if the JWT is invalid or if the JWS could not be verified + */ + @Override + protected Map decode(String token) { + Map headers = this.jwtHeaderConverter.convert(token); + + // Validate "kid" or "x5t" header + String keyIdHeader = headers.get(KEY_ID); + String x5tHeader = headers.get(X5T); + if (keyIdHeader == null && x5tHeader == null) { + throw new InvalidTokenException("Invalid JWT/JWS: " + KEY_ID + " or " + X5T + " is a required JOSE Header"); + } + JwkDefinitionSource.JwkDefinitionHolder jwkDefinitionHolder = this.jwkDefinitionSource.getDefinitionLoadIfNecessary(keyIdHeader, x5tHeader); + if (jwkDefinitionHolder == null) { + throw new InvalidTokenException("Invalid JOSE Header " + KEY_ID + " (" + keyIdHeader + "), " + X5T + " (" + x5tHeader + ")"); + } + + JwkDefinition jwkDefinition = jwkDefinitionHolder.getJwkDefinition(); + // Validate "alg" header + String algorithmHeader = headers.get(ALGORITHM); + if (algorithmHeader == null) { + throw new InvalidTokenException("Invalid JWT/JWS: " + ALGORITHM + " is a required JOSE Header"); + } + if (jwkDefinition.getAlgorithm() != null && !algorithmHeader.equals(jwkDefinition.getAlgorithm().headerParamValue())) { + throw new InvalidTokenException("Invalid JOSE Header " + ALGORITHM + " (" + algorithmHeader + ")" + + " does not match algorithm associated to JWK with " + KEY_ID + " (" + keyIdHeader + "), " + X5T + " (" + x5tHeader + ")"); + } + + // Verify signature + SignatureVerifier verifier = jwkDefinitionHolder.getSignatureVerifier(); + Jwt jwt; + try { + jwt = JwtHelper.decode(token); + jwt.verifySignature(verifier); + } catch (Exception ex) { + throw new InvalidTokenException("Failed to decode/verify JWT/JWS", ex); + } + + Map claims = this.jsonParser.parseMap(jwt.getClaims()); + if (claims.containsKey(EXP) && claims.get(EXP) instanceof Integer) { + Integer expiryInt = (Integer) claims.get(EXP); + claims.put(EXP, new Long(expiryInt)); + } + this.getJwtClaimsSetVerifier().verify(claims); + + return claims; + } + + /** + * This operation (JWT signing) is not supported and if called, + * will throw a {@link JwkException}. + * + * @throws JwkException + */ + @Override + protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { + throw new JwkException("JWT signing (JWS) is not supported."); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java new file mode 100644 index 000000000..cc435012d --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.token.store.jwk; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.jwt.codec.Codecs; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * A {@link Converter} that converts the supplied String representation of a JWT + * to a Map of JWT Header Parameters. + * + * @see JSON Web Token (JWT) + * + * @author Joe Grandja + * @author Vedran Pavic + */ +class JwtHeaderConverter implements Converter> { + private final JsonFactory factory = new JsonFactory(); + + /** + * Converts the supplied JSON Web Token to a Map of JWT Header Parameters. + * + * @param token the JSON Web Token + * @return a Map of JWT Header Parameters + * @throws InvalidTokenException if the JWT is invalid + */ + @Override + public Map convert(String token) { + Map headers; + + int headerEndIndex = token.indexOf('.'); + if (headerEndIndex == -1) { + throw new InvalidTokenException("Invalid JWT. Missing JOSE Header."); + } + + byte[] decodedHeader; + + try { + decodedHeader = Codecs.b64UrlDecode(token.substring(0, headerEndIndex)); + } catch (IllegalArgumentException ex) { + throw new InvalidTokenException("Invalid JWT. Malformed JOSE Header.", ex); + } + + JsonParser parser = null; + + try { + parser = this.factory.createParser(decodedHeader); + headers = new HashMap(); + if (parser.nextToken() == JsonToken.START_OBJECT) { + while (parser.nextToken() == JsonToken.FIELD_NAME) { + String headerName = parser.getCurrentName(); + parser.nextToken(); + String headerValue = parser.getValueAsString(); + headers.put(headerName, headerValue); + } + } + + } catch (IOException ex) { + throw new InvalidTokenException("An I/O error occurred while reading the JWT: " + ex.getMessage(), ex); + } finally { + try { + if (parser != null) parser.close(); + } catch (IOException ex) { } + } + + return headers; + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RsaJwkDefinition.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RsaJwkDefinition.java new file mode 100644 index 000000000..5eada32c4 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RsaJwkDefinition.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.token.store.jwk; + +/** + * A JSON Web Key (JWK) representation of a RSA key. + * + * @see JSON Web Key (JWK) + * @see JSON Web Algorithms (JWA) + * + * @author Joe Grandja + */ +final class RsaJwkDefinition extends JwkDefinition { + private final String modulus; + private final String exponent; + + /** + * Creates an instance of a RSA JSON Web Key (JWK). + * + * @param keyId the Key ID + * @param x5t the X.509 Certificate SHA-1 Thumbprint ("x5t") + * @param publicKeyUse the intended use of the Public Key + * @param algorithm the algorithm intended to be used + * @param modulus the modulus value for the Public Key + * @param exponent the exponent value for the Public Key + */ + RsaJwkDefinition(String keyId, + String x5t, + PublicKeyUse publicKeyUse, + CryptoAlgorithm algorithm, + String modulus, + String exponent) { + super(keyId, x5t, KeyType.RSA, publicKeyUse, algorithm); + this.modulus = modulus; + this.exponent = exponent; + } + + /** + * @return the modulus value for the Public Key + */ + String getModulus() { + return this.modulus; + } + + /** + * @return the exponent value for the Public Key + */ + String getExponent() { + return this.exponent; + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/BaseRedisTokenStoreSerializationStrategy.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/BaseRedisTokenStoreSerializationStrategy.java new file mode 100644 index 000000000..4a1075d8b --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/BaseRedisTokenStoreSerializationStrategy.java @@ -0,0 +1,60 @@ +package org.springframework.security.oauth2.provider.token.store.redis; + +/** + * Handles null/empty byte arrays on deserialize and null objects on serialize. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author efenderbosch + */ +@Deprecated +public abstract class BaseRedisTokenStoreSerializationStrategy implements RedisTokenStoreSerializationStrategy { + + private static final byte[] EMPTY_ARRAY = new byte[0]; + + private static boolean isEmpty(byte[] bytes) { + return bytes == null || bytes.length == 0; + } + + @Override + public T deserialize(byte[] bytes, Class clazz) { + if (isEmpty(bytes)) { + return null; + } + return deserializeInternal(bytes, clazz); + } + + protected abstract T deserializeInternal(byte[] bytes, Class clazz); + + @Override + public String deserializeString(byte[] bytes) { + if (isEmpty(bytes)) { + return null; + } + return deserializeStringInternal(bytes); + } + + protected abstract String deserializeStringInternal(byte[] bytes); + + @Override + public byte[] serialize(Object object) { + if (object == null) { + return EMPTY_ARRAY; + } + return serializeInternal(object); + } + + protected abstract byte[] serializeInternal(Object object); + + @Override + public byte[] serialize(String data) { + if (data == null) { + return EMPTY_ARRAY; + } + return serializeInternal(data); + } + + protected abstract byte[] serializeInternal(String data); + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/JdkSerializationStrategy.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/JdkSerializationStrategy.java new file mode 100644 index 000000000..373eb652e --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/JdkSerializationStrategy.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-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.provider.token.store.redis; + +import org.springframework.core.serializer.support.SerializationFailedException; + +import java.io.Serializable; + +import org.springframework.security.oauth2.common.util.SerializationUtils; + +/** + * Serializes and deserializes allowed objects using {@link SerializationUtils}. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author efenderbosch + * @author Artem Smotrakov + */ +@Deprecated +public class JdkSerializationStrategy extends StandardStringSerializationStrategy { + + private static final byte[] EMPTY_ARRAY = new byte[0]; + + @Override + @SuppressWarnings("unchecked") + protected T deserializeInternal(byte[] bytes, Class clazz) { + if (bytes == null || bytes.length == 0) { + return null; + } + try { + return (T) SerializationUtils.deserialize(bytes); + } catch (Exception e) { + throw new SerializationFailedException("Failed to deserialize payload", e); + } + } + + @Override + protected byte[] serializeInternal(Object object) { + if (object == null) { + return EMPTY_ARRAY; + } + if (!(object instanceof Serializable)) { + throw new IllegalArgumentException(this.getClass().getSimpleName() + + " requires a Serializable payload but received an object of type [" + + object.getClass().getName() + "]"); + } + + try { + return SerializationUtils.serialize(object); + } catch (Exception e) { + throw new SerializationFailedException("Failed to serialize object", e); + } + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/RedisTokenStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/RedisTokenStore.java new file mode 100644 index 000000000..519ffbea5 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/RedisTokenStore.java @@ -0,0 +1,465 @@ +package org.springframework.security.oauth2.provider.token.store.redis; + +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.ScanOptions; +import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2RefreshToken; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator; +import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author efenderbosch + */ +@Deprecated +public class RedisTokenStore implements TokenStore { + + private static final String ACCESS = "access:"; + private static final String AUTH_TO_ACCESS = "auth_to_access:"; + private static final String AUTH = "auth:"; + private static final String REFRESH_AUTH = "refresh_auth:"; + private static final String ACCESS_TO_REFRESH = "access_to_refresh:"; + private static final String REFRESH = "refresh:"; + private static final String REFRESH_TO_ACCESS = "refresh_to_access:"; + private static final String CLIENT_ID_TO_ACCESS = "client_id_to_access:"; + private static final String UNAME_TO_ACCESS = "uname_to_access:"; + + private static final boolean springDataRedis_2_0 = ClassUtils.isPresent( + "org.springframework.data.redis.connection.RedisStandaloneConfiguration", + RedisTokenStore.class.getClassLoader()); + + private final RedisConnectionFactory connectionFactory; + private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator(); + private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy(); + + private String prefix = ""; + + private Method redisConnectionSet_2_0; + + public RedisTokenStore(RedisConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + if (springDataRedis_2_0) { + this.loadRedisConnectionMethods_2_0(); + } + } + + public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) { + this.authenticationKeyGenerator = authenticationKeyGenerator; + } + + public void setSerializationStrategy(RedisTokenStoreSerializationStrategy serializationStrategy) { + this.serializationStrategy = serializationStrategy; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + private void loadRedisConnectionMethods_2_0() { + this.redisConnectionSet_2_0 = ReflectionUtils.findMethod( + RedisConnection.class, "set", byte[].class, byte[].class); + } + + private RedisConnection getConnection() { + return connectionFactory.getConnection(); + } + + private byte[] serialize(Object object) { + return serializationStrategy.serialize(object); + } + + private byte[] serializeKey(String object) { + return serialize(prefix + object); + } + + private OAuth2AccessToken deserializeAccessToken(byte[] bytes) { + return serializationStrategy.deserialize(bytes, OAuth2AccessToken.class); + } + + private OAuth2Authentication deserializeAuthentication(byte[] bytes) { + return serializationStrategy.deserialize(bytes, OAuth2Authentication.class); + } + + private OAuth2RefreshToken deserializeRefreshToken(byte[] bytes) { + return serializationStrategy.deserialize(bytes, OAuth2RefreshToken.class); + } + + private byte[] serialize(String string) { + return serializationStrategy.serialize(string); + } + + private String deserializeString(byte[] bytes) { + return serializationStrategy.deserializeString(bytes); + } + + @Override + public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { + String key = authenticationKeyGenerator.extractKey(authentication); + byte[] serializedKey = serializeKey(AUTH_TO_ACCESS + key); + byte[] bytes = null; + RedisConnection conn = getConnection(); + try { + bytes = conn.get(serializedKey); + } finally { + conn.close(); + } + OAuth2AccessToken accessToken = deserializeAccessToken(bytes); + if (accessToken != null) { + OAuth2Authentication storedAuthentication = readAuthentication(accessToken.getValue()); + if ((storedAuthentication == null || !key.equals(authenticationKeyGenerator.extractKey(storedAuthentication)))) { + // Keep the stores consistent (maybe the same user is + // represented by this authentication but the details have + // changed) + storeAccessToken(accessToken, authentication); + } + + } + return accessToken; + } + + @Override + public OAuth2Authentication readAuthentication(OAuth2AccessToken token) { + return readAuthentication(token.getValue()); + } + + @Override + public OAuth2Authentication readAuthentication(String token) { + byte[] bytes = null; + RedisConnection conn = getConnection(); + try { + bytes = conn.get(serializeKey(AUTH + token)); + } finally { + conn.close(); + } + OAuth2Authentication auth = deserializeAuthentication(bytes); + return auth; + } + + @Override + public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) { + return readAuthenticationForRefreshToken(token.getValue()); + } + + public OAuth2Authentication readAuthenticationForRefreshToken(String token) { + RedisConnection conn = getConnection(); + try { + byte[] bytes = conn.get(serializeKey(REFRESH_AUTH + token)); + OAuth2Authentication auth = deserializeAuthentication(bytes); + return auth; + } finally { + conn.close(); + } + } + + @Override + public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { + byte[] serializedAccessToken = serialize(token); + byte[] serializedAuth = serialize(authentication); + byte[] accessKey = serializeKey(ACCESS + token.getValue()); + byte[] authKey = serializeKey(AUTH + token.getValue()); + byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication)); + byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication)); + byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); + + RedisConnection conn = getConnection(); + try { + conn.openPipeline(); + if (springDataRedis_2_0) { + try { + this.redisConnectionSet_2_0.invoke(conn, accessKey, serializedAccessToken); + this.redisConnectionSet_2_0.invoke(conn, authKey, serializedAuth); + this.redisConnectionSet_2_0.invoke(conn, authToAccessKey, serializedAccessToken); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } else { + conn.set(accessKey, serializedAccessToken); + conn.set(authKey, serializedAuth); + conn.set(authToAccessKey, serializedAccessToken); + } + if (!authentication.isClientOnly()) { + conn.sAdd(approvalKey, serializedAccessToken); + } + conn.sAdd(clientId, serializedAccessToken); + if (token.getExpiration() != null) { + int seconds = token.getExpiresIn(); + conn.expire(accessKey, seconds); + conn.expire(authKey, seconds); + conn.expire(authToAccessKey, seconds); + conn.expire(clientId, seconds); + conn.expire(approvalKey, seconds); + } + OAuth2RefreshToken refreshToken = token.getRefreshToken(); + if (refreshToken != null && refreshToken.getValue() != null) { + byte[] refresh = serialize(refreshToken.getValue()); + byte[] access = serialize(token.getValue()); + byte[] refreshToAccessKey = serializeKey(REFRESH_TO_ACCESS + refreshToken.getValue()); + byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + token.getValue()); + if (springDataRedis_2_0) { + try { + this.redisConnectionSet_2_0.invoke(conn, refreshToAccessKey, access); + this.redisConnectionSet_2_0.invoke(conn, accessToRefreshKey, refresh); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } else { + conn.set(refreshToAccessKey, access); + conn.set(accessToRefreshKey, refresh); + } + if (refreshToken instanceof ExpiringOAuth2RefreshToken) { + ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken; + Date expiration = expiringRefreshToken.getExpiration(); + if (expiration != null) { + int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L) + .intValue(); + conn.expire(refreshToAccessKey, seconds); + conn.expire(accessToRefreshKey, seconds); + } + } + } + conn.closePipeline(); + } finally { + conn.close(); + } + } + + private static String getApprovalKey(OAuth2Authentication authentication) { + String userName = authentication.getUserAuthentication() == null ? "" + : authentication.getUserAuthentication().getName(); + return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName); + } + + private static String getApprovalKey(String clientId, String userName) { + return clientId + (userName == null ? "" : ":" + userName); + } + + @Override + public void removeAccessToken(OAuth2AccessToken accessToken) { + removeAccessToken(accessToken.getValue()); + } + + @Override + public OAuth2AccessToken readAccessToken(String tokenValue) { + byte[] key = serializeKey(ACCESS + tokenValue); + byte[] bytes = null; + RedisConnection conn = getConnection(); + try { + bytes = conn.get(key); + } finally { + conn.close(); + } + OAuth2AccessToken accessToken = deserializeAccessToken(bytes); + return accessToken; + } + + public void removeAccessToken(String tokenValue) { + byte[] accessKey = serializeKey(ACCESS + tokenValue); + byte[] authKey = serializeKey(AUTH + tokenValue); + byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue); + RedisConnection conn = getConnection(); + try { + conn.openPipeline(); + conn.get(accessKey); + conn.get(authKey); + conn.del(accessKey); + conn.del(accessToRefreshKey); + // Don't remove the refresh token - it's up to the caller to do that + conn.del(authKey); + List results = conn.closePipeline(); + byte[] access = (byte[]) results.get(0); + byte[] auth = (byte[]) results.get(1); + + OAuth2Authentication authentication = deserializeAuthentication(auth); + if (authentication != null) { + String key = authenticationKeyGenerator.extractKey(authentication); + byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + key); + byte[] unameKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication)); + byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); + conn.openPipeline(); + conn.del(authToAccessKey); + conn.sRem(unameKey, access); + conn.sRem(clientId, access); + conn.del(serialize(ACCESS + key)); + conn.closePipeline(); + } + } finally { + conn.close(); + } + } + + @Override + public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) { + byte[] refreshKey = serializeKey(REFRESH + refreshToken.getValue()); + byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + refreshToken.getValue()); + byte[] serializedRefreshToken = serialize(refreshToken); + RedisConnection conn = getConnection(); + try { + conn.openPipeline(); + if (springDataRedis_2_0) { + try { + this.redisConnectionSet_2_0.invoke(conn, refreshKey, serializedRefreshToken); + this.redisConnectionSet_2_0.invoke(conn, refreshAuthKey, serialize(authentication)); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } else { + conn.set(refreshKey, serializedRefreshToken); + conn.set(refreshAuthKey, serialize(authentication)); + } + if (refreshToken instanceof ExpiringOAuth2RefreshToken) { + ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken; + Date expiration = expiringRefreshToken.getExpiration(); + if (expiration != null) { + int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L) + .intValue(); + conn.expire(refreshKey, seconds); + conn.expire(refreshAuthKey, seconds); + } + } + conn.closePipeline(); + } finally { + conn.close(); + } + } + + @Override + public OAuth2RefreshToken readRefreshToken(String tokenValue) { + byte[] key = serializeKey(REFRESH + tokenValue); + byte[] bytes = null; + RedisConnection conn = getConnection(); + try { + bytes = conn.get(key); + } finally { + conn.close(); + } + OAuth2RefreshToken refreshToken = deserializeRefreshToken(bytes); + return refreshToken; + } + + @Override + public void removeRefreshToken(OAuth2RefreshToken refreshToken) { + removeRefreshToken(refreshToken.getValue()); + } + + public void removeRefreshToken(String tokenValue) { + byte[] refreshKey = serializeKey(REFRESH + tokenValue); + byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + tokenValue); + byte[] refresh2AccessKey = serializeKey(REFRESH_TO_ACCESS + tokenValue); + RedisConnection conn = getConnection(); + try { + conn.openPipeline(); + conn.del(refreshKey); + conn.del(refreshAuthKey); + conn.get(refresh2AccessKey); + conn.del(refresh2AccessKey); + List results = conn.closePipeline(); + + byte[] accessTokenBytes = (byte[]) results.get(2); + if(accessTokenBytes != null) { + String accessTokenValue = deserializeString(accessTokenBytes); + byte[] access2RefreshKey = serializeKey(ACCESS_TO_REFRESH + accessTokenValue); + conn.del(access2RefreshKey); + } + + } finally { + conn.close(); + } + } + + @Override + public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { + removeAccessTokenUsingRefreshToken(refreshToken.getValue()); + } + + private void removeAccessTokenUsingRefreshToken(String refreshToken) { + byte[] key = serializeKey(REFRESH_TO_ACCESS + refreshToken); + List results = null; + RedisConnection conn = getConnection(); + try { + conn.openPipeline(); + conn.get(key); + conn.del(key); + results = conn.closePipeline(); + } finally { + conn.close(); + } + if (results == null) { + return; + } + byte[] bytes = (byte[]) results.get(0); + String accessToken = deserializeString(bytes); + if (accessToken != null) { + removeAccessToken(accessToken); + } + } + + private List getByteLists(byte[] approvalKey, RedisConnection conn) { + List byteList; + Long size = conn.sCard(approvalKey); + byteList = new ArrayList(size.intValue()); + Cursor cursor = conn.sScan(approvalKey, ScanOptions.NONE); + while(cursor.hasNext()) { + byteList.add(cursor.next()); + } + return byteList; + } + + @Override + public Collection findTokensByClientIdAndUserName(String clientId, String userName) { + byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(clientId, userName)); + List byteList = null; + RedisConnection conn = getConnection(); + try { + byteList = getByteLists(approvalKey, conn); + } finally { + conn.close(); + } + if (byteList == null || byteList.size() == 0) { + return Collections. emptySet(); + } + List accessTokens = new ArrayList(byteList.size()); + for (byte[] bytes : byteList) { + OAuth2AccessToken accessToken = deserializeAccessToken(bytes); + accessTokens.add(accessToken); + } + return Collections. unmodifiableCollection(accessTokens); + } + + @Override + public Collection findTokensByClientId(String clientId) { + byte[] key = serializeKey(CLIENT_ID_TO_ACCESS + clientId); + List byteList = null; + RedisConnection conn = getConnection(); + try { + byteList = getByteLists(key, conn); + } finally { + conn.close(); + } + if (byteList == null || byteList.size() == 0) { + return Collections. emptySet(); + } + List accessTokens = new ArrayList(byteList.size()); + for (byte[] bytes : byteList) { + OAuth2AccessToken accessToken = deserializeAccessToken(bytes); + accessTokens.add(accessToken); + } + return Collections. unmodifiableCollection(accessTokens); + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/RedisTokenStoreSerializationStrategy.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/RedisTokenStoreSerializationStrategy.java new file mode 100644 index 000000000..cf48c2373 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/RedisTokenStoreSerializationStrategy.java @@ -0,0 +1,20 @@ +package org.springframework.security.oauth2.provider.token.store.redis; + +/** + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author efenderbosch + */ +@Deprecated +public interface RedisTokenStoreSerializationStrategy { + + T deserialize(byte[] bytes, Class clazz); + + String deserializeString(byte[] bytes); + + byte[] serialize(Object object); + + byte[] serialize(String data); + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/StandardStringSerializationStrategy.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/StandardStringSerializationStrategy.java new file mode 100644 index 000000000..a50545963 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/redis/StandardStringSerializationStrategy.java @@ -0,0 +1,29 @@ +package org.springframework.security.oauth2.provider.token.store.redis; + +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * Serializes Strings using {@link StringRedisSerializer} + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author efenderbosch + * + */ +@Deprecated +public abstract class StandardStringSerializationStrategy extends BaseRedisTokenStoreSerializationStrategy { + + private static final StringRedisSerializer STRING_SERIALIZER = new StringRedisSerializer(); + + @Override + protected String deserializeStringInternal(byte[] bytes) { + return STRING_SERIALIZER.deserialize(bytes); + } + + @Override + protected byte[] serializeInternal(String string) { + return STRING_SERIALIZER.serialize(string); + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/vote/ClientScopeVoter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/vote/ClientScopeVoter.java new file mode 100644 index 000000000..88f6ccd8f --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/vote/ClientScopeVoter.java @@ -0,0 +1,140 @@ +package org.springframework.security.oauth2.provider.vote; + +import java.util.Collection; +import java.util.Set; + +import org.springframework.security.access.AccessDecisionVoter; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.ConfigAttribute; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; + +/** + * This voter checks scope in request is consistent with that held by the client. If there is no user in the request + * (client_credentials grant) it checks against authorities of client instead of scopes by default. Activate by adding + * CLIENT_HAS_SCOPE to security attributes. + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * + * @author Dave Syer + * + */ +@Deprecated +public class ClientScopeVoter implements AccessDecisionVoter { + + private String clientHasScope = "CLIENT_HAS_SCOPE"; + + private boolean throwException = true; + + private ClientDetailsService clientDetailsService; + + private boolean clientAuthoritiesAreScopes = true; + + /** + * ClientDetailsService for looking up clients by ID. + * + * @param clientDetailsService the client details service (mandatory) + */ + public void setClientDetailsService(ClientDetailsService clientDetailsService) { + this.clientDetailsService = clientDetailsService; + } + + /** + * Flag to determine the behaviour on access denied. If set then we throw an {@link InsufficientScopeException} + * instead of returning {@link AccessDecisionVoter#ACCESS_DENIED}. This is unconventional for an access decision + * voter because it vetos the other voters in the chain, but it enables us to pass a message to the caller with + * information about the required scope. + * + * @param throwException the flag to set (default true) + */ + public void setThrowException(boolean throwException) { + this.throwException = throwException; + } + + /** + * Flag to signal that when there is no user authentication client authorities are to be treated as scopes. + * + * @param clientAuthoritiesAreScopes the flag value (default true) + */ + public void setClientAuthoritiesAreScopes(boolean clientAuthoritiesAreScopes) { + this.clientAuthoritiesAreScopes = clientAuthoritiesAreScopes; + } + + /** + * The name of the config attribute that can be used to deny access to OAuth2 client. Defaults to + * DENY_OAUTH. + * + * @param denyAccess the deny access attribute value to set + */ + public void setDenyAccess(String denyAccess) { + this.clientHasScope = denyAccess; + } + + public boolean supports(ConfigAttribute attribute) { + if (clientHasScope.equals(attribute.getAttribute())) { + return true; + } + else { + return false; + } + } + + /** + * This implementation supports any type of class, because it does not query the presented secure object. + * + * @param clazz the secure object + * + * @return always true + */ + public boolean supports(Class clazz) { + return true; + } + + public int vote(Authentication authentication, Object object, Collection attributes) { + + int result = ACCESS_ABSTAIN; + + if (!(authentication instanceof OAuth2Authentication)) { + return result; + } + + OAuth2Authentication oauth2Authentication = (OAuth2Authentication) authentication; + OAuth2Request clientAuthentication = oauth2Authentication.getOAuth2Request(); + ClientDetails client = clientDetailsService.loadClientByClientId(clientAuthentication.getClientId()); + Set scopes = clientAuthentication.getScope(); + if (oauth2Authentication.isClientOnly() && clientAuthoritiesAreScopes) { + scopes = AuthorityUtils.authorityListToSet(clientAuthentication.getAuthorities()); + } + + for (ConfigAttribute attribute : attributes) { + if (this.supports(attribute)) { + + result = ACCESS_GRANTED; + + for (String scope : scopes) { + if (!client.getScope().contains(scope)) { + result = ACCESS_DENIED; + break; + } + } + + if (result == ACCESS_DENIED && throwException) { + InsufficientScopeException failure = new InsufficientScopeException( + "Insufficient scope for this resource", client.getScope()); + throw new AccessDeniedException(failure.getMessage(), failure); + } + + return result; + } + } + + return result; + } + +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/vote/ScopeVoter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/vote/ScopeVoter.java index 86a3cd7f1..f98ccf436 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/vote/ScopeVoter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/vote/ScopeVoter.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -27,6 +27,7 @@ import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; /** *

@@ -39,7 +40,7 @@ *

* Abstains from voting if no configuration attribute commences with the scope prefix, or if the current * Authentication is not a {@link OAuth2Authentication} or the current client authentication is not a - * {@link AuthorizationRequest} (which contains teh scope data). Votes to grant access if there is an exact matching + * {@link AuthorizationRequest} (which contains the scope data). Votes to grant access if there is an exact matching * {@link AuthorizationRequest#getScope() authorized scope} to a ConfigAttribute starting with the scope * prefix. Votes to deny access if there is no exact matching authorized scope to a ConfigAttribute * starting with the scope prefix. @@ -48,13 +49,17 @@ *

* All comparisons and prefixes are case insensitive so you can use (e.g.) SCOPE_READ for simple * Facebook-like scope names that might be lower case in the resource definition, or - * scope=http://my.company.com/scopes/read/ (scopePrefix="scope=") for Google-like URI scope + * scope=https://my.company.com/scopes/read/ (scopePrefix="scope=") for Google-like URI scope * names. *

- * + * + *

+ * @deprecated See the OAuth 2.0 Migration Guide for Spring Security 5. + * * @author Dave Syer * */ +@Deprecated public class ScopeVoter implements AccessDecisionVoter { private String scopePrefix = "SCOPE_"; @@ -131,7 +136,7 @@ public int vote(Authentication authentication, Object object, Collection - + @@ -235,6 +235,15 @@ + + + + The reference to the bean that defines the + implicit grant service. + + + + @@ -370,6 +379,14 @@ + + + + + The reference to the bean that defines the AuthenticationDetailsSource. + + + @@ -511,7 +528,7 @@ Element for declaring and configuring an expression handler for oauth security expressions. See - http://static.springsource.org/spring-security/site/docs/3.0.x/reference/el-access.html + https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html @@ -528,7 +545,7 @@ handler for oauth security expressions in http intercept urls. See - http://static.springsource.org/spring-security/site/docs/3.0.x/reference/el-access.html + https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html @@ -689,6 +706,20 @@ + + + + The username for authentication, required only when type is "password". + + + + + + + The password for authentication, required only when type is "password". + + + diff --git a/spring-security-oauth2/src/main/resources/org/springframework/security/oauth2/spring-security-oauth2-2.0.xsd b/spring-security-oauth2/src/main/resources/org/springframework/security/oauth2/spring-security-oauth2-2.0.xsd new file mode 100644 index 000000000..015710c04 --- /dev/null +++ b/spring-security-oauth2/src/main/resources/org/springframework/security/oauth2/spring-security-oauth2-2.0.xsd @@ -0,0 +1,793 @@ + + + + + + + + + Creates an OAuth2RestTemplate with all the pieces needed to connect to a remote resource from + a web + application. Injects request and session-scoped beans into the template, so can only be + used in the context of a web + request. + + + + + + + + + + + + + + + The OAuth2ProtectedResourceDetails governing the configuration of this client. Mandatory. + + + + + + + The reference to the bean that manages access token acquisition. Optional (defaults to a chain + including common grant types from the spec). + + + + + + + + + + + + Specifies that the oauth 2 authorization and token + endpoints should be created in the application + context. These are + implemented as regular Spring @Controller beans, so as long as the + default Spring MVC set up in + present in the application + the endpoints should work (at /oauth/authorization and /oauth/token by + default). + + + + + + + + + The configuration of the authorization code + mechanism. This + mechanism enables a way for clients to + obtain an + access token by obtaining an authorization code. + + + + + + + Whether to disable the authorization code + mechanism. + + + + + + + The reference to the bean that defines the + authorization code + services. Default value is an + instance of + "org.springframework.security.oauth2.provider.authorization_code.InMemoryAuthorizationCodeServices". + + + + + + + + + The configuration of the client credentials + grant type. + + + + + + + Whether to disable the implicit grant type + + + + + + + + + The configuration of the refresh token grant + type. + + + + + + + Whether to disable the refresh token grant + type + + + + + + + + + The configuration of the client credentials + grant type. + + + + + + + Whether to disable the refresh token grant + type + + + + + + + + + The configuration of the resource owner password + grant type. + + + + + + + Whether to disable the refresh token grant + type + + + + + + + A reference to an authentication manager that + can be used to + authenticate the resource owner + + + + + + + + + The configuration of your custom grant type. + + + + + + + Whether to disable this grant + type + + + + + + + A reference to your token granter + + + + + + + + + + The reference to the bean that defines the client + details service. + + + + + + + The URL at which a request for an access token + will be serviced. + Default value: "/oauth/token" + + + + + + + The URL at which a user is redirected for + authorization. Default + value: "/oauth/authorize" + + + + + + + + + The reference to the bean that defines the + granter of different oauth + token types. + + + + + + + + @deprecated (since 2.0.2 this is unnecessary). The reference to the bean that defines the + implicit grant service. + + + + + + + + The reference to the bean that defines the + OAuth2RequestValidator implementation. Default + value is an instance of + "org.springframework.security.oauth2.provider.DefaultOAuth2RequestValidator". + + + + + + + + The reference to the bean that defines the token + services. Default + value is an instance of + "org.springframework.security.oauth2.provider.token.DefaultTokenServices". + + + + + + + + The reference to the bean that defines the factory for + authorization requests from the input + parameters (e.g. request parameters). + Default + value is an + instance of + "org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory". + + + + + + + + Reference to a bean that handles user approval decisions. Using this strategy servers can + selectively skip the approval process depending on decisions in the past or on the type of client. + + + + + + + + The URL of the page that handles the user + approval form (if needed, depending on the grant type). + The default is "forward:/oauth/confirm_access" which is not handled + by the authorization endpoint, so normally you + will have to supply a handler + for this path. + + + + + + + + The URL of the page that handles errors (default forward:/oauth/error). + + + + + + + + Path for check token endpoint (defaults to /oauth/check_token). + + + + + + + + True if the check token endpoint is to be installed (must be separately protected). + + + + + + + + The name of the form parameter that is used to + indicate user + approval of the client + authentication + request. + Default value: "user_oauth_approval". + + + + + + + + The reference to the bean that defines the + redirect resolver, used + during the user + authorization. + Default + value is an instance of + "org.springframework.security.oauth2.provider.authorization_code.DefaultRedirectResolver". + + + + + + + + + + + Specifies that there are oauth 2 protected resources in + the application context. This element + has an + id which is the bean id of the filter created. The filter + should be added to the Spring Security filter chain at + position before="PRE_AUTH_FILTER" + + + + + + + + + + The resource id that is protected by this filter + if any. If empty or + absent then all resource ids + are allowed, + otherwise + only tokens which are granted to a client that contains + this reosurce + id will be legal. + + + + + + + + The reference to the bean that defines the token + services. Default + value is an instance of + "org.springframework.security.oauth2.provider.token.DefaultTokenServices". + + + + + + + + The reference to the bean that defines the authentication manager + for the incoming tokens. If provided then the resource id and token services + are ignored. Default + value is an instance of + "org.springframework.security.oauth2.provider.token.OAuth2AuthenticationManager". + + + + + + + + The reference to the bean that defines the token + extractor. Default + value is an instance of + "org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor". + + + + + + + + The reference to the bean that defines the entry point for failed authentications. Defaults to + a vanilla + org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint. + + + + + + + + The reference to the bean that defines the AuthenticationDetailsSource. + + + + + + + + Flag to say that the resource is stateless, i.e. it handles authentication on its own and it doesn't accept incoming pre-authentication. Default true. + + + + + + + + + + + + Default element that contains the definition of the + OAuth clients that are + allowed to access this + service. + + + + + + + + + + + Definition of a client that can act on behalf + of a user. + + + + + + + + The client id. + + + + + + + The client secret. If the secret is + undefined or empty (the + default) the client does + not + require a + secret. + + + + + + + The re-direct URI(s) established during + registration (optional, comma separated). + + + + + + + The resource ids to which this client can be + granted access + (comma-separated). If missing or + empty all + resources are + accessible (not recommended by the spec). + + + + + + + The scopes to which the client is limited + (comma-separated). If + scope is undefined or empty + (the + default) the client + is not limited by scope, but in that case + the authorization + service must explicitly + accept unlimited + access by not + specifying any scopes itself. + + + + + + + Grant types that are authorized for the + client to use + (comma-separated). Currently defined + grant types + include + "authorization_code", "password", "assertion", and + "refresh_token". Default value is + "authorization_code,refresh_token". + + + + + + + Authorities that are granted to the client + (comma-separated). Distinct + from the authorities + granted to + the user on behalf + of whom the client is acting. + + + + + + + Scopes or scope patterns that are autoapproved (comma-separated), or just "true" to autoapprove all. + + + + + + + The access token validity period in seconds (optional). If unspecified a global default will + be applied by the token services. + + + + + + + The refresh token validity period in seconds (optional). If unspecified a global default + will + be applied by the token services. + + + + + + + + + + + + + + + Element for declaring and configuring an expression + handler for oauth + security expressions. See + https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html + + + + + + + + + + + + + Element for declaring and configuring an expression + handler for oauth + security expressions in http + intercept urls. See + https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html + + + + + + + + + + + + + Creates the oauth 2 client filter be be added to the + application security policy. + + + + + + + + + The reference to the bean that defines the + redirect strategy, used when redirecting the user for + access authorization. Default value is an instance of + "org.springframework.security.web.DefaultRedirectStrategy". + + + + + + + + + + + + Definition of a remote resource that is protected via + OAuth2 to which this client application wants + access. + + + + + + + + + The grant type. Currently defined grant types + include + "authorization_code", "password", and + "assertion". + Default value + is "authorization_code". + + + + + + + The client id. This is the id by which the + resource server will + identify this application. + + + + + + + The uri to where the access token may be + obtained. + + + + + + + Comma-separted list of string specifying the + scope of the access to the + resource. By default, + no + scope will be + specified. + + + + + + + The secret asssociated with the resource. By + default, no secret + will be supplied for access to + the resource. + + + + + + + The scheme that is used to pass the client + secret. Suggested + values: "header" and "form". + Default: + "header". + See section 2.1 of the OAuth 2 spec. + + + + + + + The uri to which the user will be redirected if + the user is ever + needed to grant an authorization + code. + + + + + + + The method for bearing the token when accessing + the resource. + Default value is "header". See + AuthenticationScheme enum for possible values. + + + + + + + The name of the bearer token. The default is + "access_token", which + is according to the spec, + but + some providers + (e.g. Facebook) don't conform to the spec. + + + + + + + Some resource servers may require a + pre-established URI to which + they will redirect users after + users + authorize an access token. + + + + + + + Boolean flag indicating that the current URI should be used as a redirect (if available) rather + than the + registered redirect URI. Default is true. + + + + + + + The username for authentication, required only when type is "password". + + + + + + + The password for authentication, required only when type is "password". + + + + + + + + + diff --git a/spring-security-oauth2/src/test/java/org/company/oauth2/CustomAuthentication.java b/spring-security-oauth2/src/test/java/org/company/oauth2/CustomAuthentication.java new file mode 100644 index 000000000..d974c5a8f --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/company/oauth2/CustomAuthentication.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-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.company.oauth2; + +import org.springframework.security.authentication.AbstractAuthenticationToken; + +public class CustomAuthentication extends AbstractAuthenticationToken { + + private static final long serialVersionUID = 1L; + + private String principal; + + public CustomAuthentication(String name, boolean authenticated) { + super(null); + setAuthenticated(authenticated); + this.principal = name; + } + + public Object getCredentials() { + return null; + } + + public Object getPrincipal() { + return this.principal; + } +} diff --git a/spring-security-oauth2/src/test/java/org/company/oauth2/CustomOAuth2AccessToken.java b/spring-security-oauth2/src/test/java/org/company/oauth2/CustomOAuth2AccessToken.java new file mode 100644 index 000000000..06e6f678f --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/company/oauth2/CustomOAuth2AccessToken.java @@ -0,0 +1,26 @@ +/* + * Copyright 2012-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.company.oauth2; + +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; + +public class CustomOAuth2AccessToken extends DefaultOAuth2AccessToken { + + public CustomOAuth2AccessToken(String value) { + super(value); + } +} diff --git a/spring-security-oauth2/src/test/java/org/company/oauth2/CustomOAuth2Authentication.java b/spring-security-oauth2/src/test/java/org/company/oauth2/CustomOAuth2Authentication.java new file mode 100644 index 000000000..95f6cb897 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/company/oauth2/CustomOAuth2Authentication.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-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.company.oauth2; + +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; + +public class CustomOAuth2Authentication extends OAuth2Authentication { + + public CustomOAuth2Authentication( + OAuth2Request storedRequest, + Authentication userAuthentication) { + super(storedRequest, userAuthentication); + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/AdhocTestSuite.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/AdhocTestSuite.java new file mode 100644 index 000000000..91b80aa64 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/AdhocTestSuite.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2014 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; + +import org.junit.Ignore; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; +import org.springframework.security.oauth2.config.annotation.AuthorizationServerConfigurationTests; +import org.springframework.security.oauth2.config.annotation.ResourceServerConfigurationTests; + +/** + * A test suite for probing weird ordering problems in the tests. + * + * @author Dave Syer + */ +@RunWith(Suite.class) +@SuiteClasses({ AuthorizationServerConfigurationTests.class, ResourceServerConfigurationTests.class }) +@Ignore +public class AdhocTestSuite { + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/DefaultOAuth2ClientContextTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/DefaultOAuth2ClientContextTests.java new file mode 100644 index 000000000..9cfbfc2c6 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/DefaultOAuth2ClientContextTests.java @@ -0,0 +1,21 @@ +package org.springframework.security.oauth2.client; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class DefaultOAuth2ClientContextTests { + + @Test + public void resetsState() { + DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext(); + clientContext.setPreservedState("state1", "some-state-1"); + clientContext.setPreservedState("state2", "some-state-2"); + clientContext.setPreservedState("state3", "some-state-3"); + assertNull(clientContext.removePreservedState("state1")); + assertNull(clientContext.removePreservedState("state2")); + assertEquals("some-state-3", clientContext.removePreservedState("state3")); + } + +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/DefaultOAuth2RequestAuthenticatorTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/DefaultOAuth2RequestAuthenticatorTests.java new file mode 100644 index 000000000..6ca39d0ec --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/DefaultOAuth2RequestAuthenticatorTests.java @@ -0,0 +1,99 @@ +/* + * Copyright 2013-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.client; + +import org.junit.Test; +import org.springframework.mock.http.client.MockClientHttpRequest; +import org.springframework.security.oauth2.client.http.AccessTokenRequiredException; +import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; + +import static org.junit.Assert.assertEquals; + +/** + * @author Dave Syer + * + */ +public class DefaultOAuth2RequestAuthenticatorTests { + + private DefaultOAuth2RequestAuthenticator authenticator = new DefaultOAuth2RequestAuthenticator(); + + private MockClientHttpRequest request = new MockClientHttpRequest(); + + private DefaultOAuth2ClientContext context = new DefaultOAuth2ClientContext(); + + @Test(expected = AccessTokenRequiredException.class) + public void missingAccessToken() { + BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails(); + authenticator.authenticate(resource, new DefaultOAuth2ClientContext(), request); + } + + @Test + public void addsAccessToken() { + context.setAccessToken(new DefaultOAuth2AccessToken("FOO")); + BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails(); + authenticator.authenticate(resource, context, request); + String header = request.getHeaders().getFirst("Authorization"); + assertEquals("Bearer FOO", header); + } + + // gh-1346 + @Test + public void authenticateWhenTokenTypeBearerUppercaseThenUseBearer() { + DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("FOO"); + accessToken.setTokenType(OAuth2AccessToken.BEARER_TYPE.toUpperCase()); + context.setAccessToken(accessToken); + BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails(); + authenticator.authenticate(resource, context, request); + String header = request.getHeaders().getFirst("Authorization"); + assertEquals("Bearer FOO", header); + } + + // gh-1346 + @Test + public void authenticateWhenTokenTypeBearerLowercaseThenUseBearer() { + DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("FOO"); + accessToken.setTokenType(OAuth2AccessToken.BEARER_TYPE.toLowerCase()); + context.setAccessToken(accessToken); + BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails(); + authenticator.authenticate(resource, context, request); + String header = request.getHeaders().getFirst("Authorization"); + assertEquals("Bearer FOO", header); + } + + // gh-1346 + @Test + public void authenticateWhenTokenTypeBearerMixcaseThenUseBearer() { + DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("FOO"); + accessToken.setTokenType("BeaRer"); + context.setAccessToken(accessToken); + BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails(); + authenticator.authenticate(resource, context, request); + String header = request.getHeaders().getFirst("Authorization"); + assertEquals("Bearer FOO", header); + } + + // gh-1346 + @Test + public void authenticateWhenTokenTypeMACThenUseMAC() { + DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("FOO"); + accessToken.setTokenType("MAC"); + context.setAccessToken(accessToken); + BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails(); + authenticator.authenticate(resource, context, request); + String header = request.getHeaders().getFirst("Authorization"); + assertEquals("MAC FOO", header); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/TestOAuth2RestTemplate.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/OAuth2RestTemplateTests.java similarity index 65% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/TestOAuth2RestTemplate.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/OAuth2RestTemplateTests.java index c06412f37..1c419fde9 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/TestOAuth2RestTemplate.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/OAuth2RestTemplateTests.java @@ -7,6 +7,7 @@ import static org.junit.Assert.fail; import java.io.IOException; +import java.lang.reflect.Field; import java.net.URI; import java.util.Collections; import java.util.Date; @@ -27,10 +28,12 @@ import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; import org.springframework.security.oauth2.client.token.AccessTokenProvider; +import org.springframework.security.oauth2.client.token.AccessTokenProviderChain; import org.springframework.security.oauth2.client.token.AccessTokenRequest; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken; +import org.springframework.util.ReflectionUtils; import org.springframework.web.client.RequestCallback; import org.springframework.web.client.ResponseExtractor; import org.springframework.web.util.UriTemplate; @@ -39,7 +42,7 @@ * @author Ryan Heaton * @author Dave Syer */ -public class TestOAuth2RestTemplate { +public class OAuth2RestTemplateTests { private BaseOAuth2ProtectedResourceDetails resource; @@ -67,6 +70,32 @@ public void open() throws Exception { Mockito.when(request.execute()).thenReturn(response); } + @Test + public void testNonBearerToken() throws Exception { + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("12345"); + token.setTokenType("MINE"); + restTemplate.getOAuth2ClientContext().setAccessToken(token); + ClientHttpRequest http = restTemplate.createRequest(URI.create("https://nowhere.com/api/crap"), HttpMethod.GET); + String auth = http.getHeaders().getFirst("Authorization"); + assertTrue(auth.startsWith("MINE ")); + } + + @Test + public void testCustomAuthenticator() throws Exception { + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("12345"); + token.setTokenType("MINE"); + restTemplate.setAuthenticator(new OAuth2RequestAuthenticator() { + @Override + public void authenticate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext clientContext, ClientHttpRequest req) { + req.getHeaders().set("X-Authorization", clientContext.getAccessToken().getTokenType() + " " + "Nah-nah-na-nah-nah"); + } + }); + restTemplate.getOAuth2ClientContext().setAccessToken(token); + ClientHttpRequest http = restTemplate.createRequest(URI.create("https://nowhere.com/api/crap"), HttpMethod.GET); + String auth = http.getHeaders().getFirst("X-Authorization"); + assertEquals("MINE Nah-nah-na-nah-nah", auth); + } + /** * tests appendQueryParameter */ @@ -140,7 +169,7 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO throw new AccessTokenRequiredException(resource); } }); - restTemplate.doExecute(new URI("http://foo/"), HttpMethod.GET, new NullRequestCallback(), + restTemplate.doExecute(new URI("https://foo/"), HttpMethod.GET, new NullRequestCallback(), new SimpleResponseExtractor()); } @@ -158,7 +187,7 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO return request; } }); - Boolean result = restTemplate.doExecute(new URI("http://foo/"), HttpMethod.GET, new NullRequestCallback(), + Boolean result = restTemplate.doExecute(new URI("https://foo/"), HttpMethod.GET, new NullRequestCallback(), new SimpleResponseExtractor()); assertTrue(result); } @@ -174,6 +203,86 @@ public void testNewTokenAcquiredIfExpired() throws Exception { assertTrue(!token.equals(newToken)); } + // gh-1478 + @Test + public void testNewTokenAcquiredWithDefaultClockSkew() { + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("TEST"); + token.setExpiration(new Date(System.currentTimeMillis() + 29000)); // Default clock skew is 30 secs + restTemplate.getOAuth2ClientContext().setAccessToken(token); + restTemplate.setAccessTokenProvider(new StubAccessTokenProvider()); + OAuth2AccessToken newToken = restTemplate.getAccessToken(); + assertNotNull(newToken); + assertTrue(!token.equals(newToken)); + } + + // gh-1478 + @Test + public void testNewTokenAcquiredIfLessThanConfiguredClockSkew() { + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("TEST"); + token.setExpiration(new Date(System.currentTimeMillis() + 5000)); + restTemplate.setClockSkew(6); + restTemplate.getOAuth2ClientContext().setAccessToken(token); + restTemplate.setAccessTokenProvider(new StubAccessTokenProvider()); + OAuth2AccessToken newToken = restTemplate.getAccessToken(); + assertNotNull(newToken); + assertTrue(!token.equals(newToken)); + } + + // gh-1478 + @Test + public void testNewTokenNotAcquiredIfGreaterThanConfiguredClockSkew() { + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("TEST"); + token.setExpiration(new Date(System.currentTimeMillis() + 5000)); + restTemplate.setClockSkew(4); + restTemplate.getOAuth2ClientContext().setAccessToken(token); + restTemplate.setAccessTokenProvider(new StubAccessTokenProvider()); + OAuth2AccessToken newToken = restTemplate.getAccessToken(); + assertNotNull(newToken); + assertTrue(token.equals(newToken)); + } + + // gh-1478 + @Test(expected = IllegalArgumentException.class) + public void testNegativeClockSkew() { + restTemplate.setClockSkew(-1); + } + + // gh-1909 + @Test + public void testClockSkewPropagationIntoAccessTokenProviderChain() { + AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(Collections.emptyList()); + restTemplate.setAccessTokenProvider(accessTokenProvider); + restTemplate.setClockSkew(5); + + Field field = ReflectionUtils.findField(accessTokenProvider.getClass(), "clockSkew"); + field.setAccessible(true); + + assertEquals(5, ReflectionUtils.getField(field, accessTokenProvider)); + } + + // gh-1909 + @Test + public void testApplyClockSkewOnProvidedAccessTokenProviderChain() { + AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(Collections.emptyList()); + restTemplate.setClockSkew(5); + restTemplate.setAccessTokenProvider(accessTokenProvider); + + Field field = ReflectionUtils.findField(accessTokenProvider.getClass(), "clockSkew"); + field.setAccessible(true); + + assertEquals(5, ReflectionUtils.getField(field, accessTokenProvider)); + } + + // gh-1909 + @Test + public void testClockSkewPropagationSkippedForNonAccessTokenProviderChainInstances() { + restTemplate.setClockSkew(5); + restTemplate.setAccessTokenProvider(null); + restTemplate.setClockSkew(5); + restTemplate.setAccessTokenProvider(new StubAccessTokenProvider()); + restTemplate.setClockSkew(5); + } + @Test public void testTokenIsResetIfInvalid() throws Exception { DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("TEST"); @@ -183,14 +292,15 @@ public void testTokenIsResetIfInvalid() throws Exception { @Override public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest parameters) throws UserRedirectRequiredException, AccessDeniedException { - throw new UserRedirectRequiredException("http://foo.com/", Collections.emptyMap()); + throw new UserRedirectRequiredException("https://www.foo.com/", Collections. emptyMap()); } }); try { OAuth2AccessToken newToken = restTemplate.getAccessToken(); assertNotNull(newToken); fail("Expected UserRedirectRequiredException"); - } catch (UserRedirectRequiredException e) { + } + catch (UserRedirectRequiredException e) { // planned } // context token should be reset as it clearly is invalid at this point diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/discovery/ProviderDiscoveryClientTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/discovery/ProviderDiscoveryClientTest.java new file mode 100644 index 000000000..4a8b89e54 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/discovery/ProviderDiscoveryClientTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.client.discovery; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.apache.http.HttpHeaders; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestClientException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Joe Grandja + */ +public class ProviderDiscoveryClientTest { + private MockWebServer server; + + @Before + public void setUp() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + } + + @After + public void cleanUp() throws Exception { + this.server.shutdown(); + } + + @Test(expected = IllegalArgumentException.class) + public void discoverWhenProviderLocationUriInvalidThenThrowIllegalArgumentException() throws Exception { + new ProviderDiscoveryClient("invalid-uri"); + } + + @Test + public void discoverWhenProviderSupportsDiscoveryThenReturnProviderConfiguration() throws Exception { + this.server.enqueue(new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody( + " {\n" + + " \"issuer\": \"https://springsecurity.uaa.run.pivotal.io/oauth/token/",\n" + + " \"authorization_endpoint\": \"https://springsecurity.login.run.pivotal.io/oauth/authorize/",\n" + + " \"token_endpoint\": \"https://springsecurity.login.run.pivotal.io/oauth/token/",\n" + + " \"userinfo_endpoint\": \"https://springsecurity.login.run.pivotal.io/userinfo/",\n" + + " \"jwks_uri\": \"https://springsecurity.login.run.pivotal.io/token_keys/"\n" + + " }\n")); + + ProviderDiscoveryClient client = new ProviderDiscoveryClient(this.server.url("").toString()); + ProviderConfiguration providerConfiguration = client.discover(); + + assertNotNull(providerConfiguration); + assertEquals("https://springsecurity.uaa.run.pivotal.io/oauth/token", providerConfiguration.getIssuer().toString()); + assertEquals("https://springsecurity.login.run.pivotal.io/oauth/authorize", providerConfiguration.getAuthorizationEndpoint().toString()); + assertEquals("https://springsecurity.login.run.pivotal.io/oauth/token", providerConfiguration.getTokenEndpoint().toString()); + assertEquals("https://springsecurity.login.run.pivotal.io/userinfo", providerConfiguration.getUserInfoEndpoint().toString()); + assertEquals("https://springsecurity.login.run.pivotal.io/token_keys", providerConfiguration.getJwkSetUri().toString()); + } + + @Test(expected = RestClientException.class) + public void discoverWhenProviderDoesNotSupportDiscoveryThenThrowRestClientException() throws Exception { + this.server.enqueue(new MockResponse().setResponseCode(404)); + + ProviderDiscoveryClient client = new ProviderDiscoveryClient(this.server.url("").toString()); + client.discover(); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/filter/OAuth2ClientAuthenticationProcessingFilterTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/filter/OAuth2ClientAuthenticationProcessingFilterTests.java new file mode 100644 index 000000000..b37e46f43 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/filter/OAuth2ClientAuthenticationProcessingFilterTests.java @@ -0,0 +1,120 @@ +/* + * Copyright 2010-2012 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.client.filter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import javax.servlet.ServletException; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mockito; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.OAuth2RestOperations; +import org.springframework.security.oauth2.client.http.AccessTokenRequiredException; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.RequestTokenFactory; +import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails; +import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; + +public class OAuth2ClientAuthenticationProcessingFilterTests { + + private OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter( + "/some/url"); + + private ResourceServerTokenServices tokenServices = Mockito.mock(ResourceServerTokenServices.class); + + private OAuth2RestOperations restTemplate = Mockito.mock(OAuth2RestOperations.class); + + private OAuth2Authentication authentication; + + @Rule + public ExpectedException expected = ExpectedException.none(); + + @Test + public void testAuthentication() throws Exception { + filter.setRestTemplate(restTemplate); + filter.setTokenServices(tokenServices); + Mockito.when(restTemplate.getAccessToken()).thenReturn(new DefaultOAuth2AccessToken("FOO")); + Set scopes = new HashSet(); + scopes.addAll(Arrays.asList("read", "write")); + OAuth2Request storedOAuth2Request = RequestTokenFactory.createOAuth2Request("client", false, scopes); + this.authentication = new OAuth2Authentication(storedOAuth2Request, null); + Mockito.when(tokenServices.loadAuthentication("FOO")).thenReturn(authentication); + Authentication authentication = filter.attemptAuthentication(new MockHttpServletRequest(), null); + assertEquals(this.authentication, authentication); + Mockito.verify(restTemplate, Mockito.times(1)).getAccessToken(); + } + + @Test + public void testAuthenticationWithTokenType() throws Exception { + filter.setRestTemplate(restTemplate); + filter.setTokenServices(tokenServices); + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO"); + token.setTokenType("foo"); + Mockito.when(restTemplate.getAccessToken()).thenReturn(token); + Set scopes = new HashSet(); + scopes.addAll(Arrays.asList("read", "write")); + OAuth2Request storedOAuth2Request = RequestTokenFactory.createOAuth2Request("client", false, scopes); + this.authentication = new OAuth2Authentication(storedOAuth2Request, null); + Mockito.when(tokenServices.loadAuthentication("FOO")).thenReturn(authentication); + Authentication authentication = filter.attemptAuthentication(new MockHttpServletRequest(), null); + assertEquals("foo", ((OAuth2AuthenticationDetails) authentication.getDetails()).getTokenType()); + } + + @Test + public void testSuccessfulAuthentication() throws Exception { + filter.setRestTemplate(restTemplate); + Set scopes = new HashSet(); + scopes.addAll(Arrays.asList("read", "write")); + OAuth2Request storedOAuth2Request = RequestTokenFactory.createOAuth2Request("client", false, scopes); + this.authentication = new OAuth2Authentication(storedOAuth2Request, null); + filter.successfulAuthentication(new MockHttpServletRequest(), new MockHttpServletResponse(), null, authentication); + Mockito.verify(restTemplate, Mockito.times(1)).getAccessToken(); + } + + @Test + public void testDeniedToken() throws Exception { + filter.setRestTemplate(restTemplate); + Mockito.when(restTemplate.getAccessToken()).thenThrow(new OAuth2Exception("User denied acess token")); + expected.expect(BadCredentialsException.class); + filter.attemptAuthentication(null, null); + } + + @Test + public void testUnsuccessfulAuthentication() throws IOException, ServletException { + try { + filter.unsuccessfulAuthentication(null, null, new AccessTokenRequiredException("testing", null)); + fail("AccessTokenRedirectException must be thrown"); + } + catch (AccessTokenRequiredException ex) { + + } + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/filter/OAuth2ClientContextFilterTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/filter/OAuth2ClientContextFilterTests.java new file mode 100644 index 000000000..111fa66aa --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/filter/OAuth2ClientContextFilterTests.java @@ -0,0 +1,138 @@ +package org.springframework.security.oauth2.client.filter; + +import static org.junit.Assert.assertEquals; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; +import org.springframework.security.web.RedirectStrategy; + +/** + * @author Ryan Heaton + * @author Dave Syer + */ +public class OAuth2ClientContextFilterTests { + + @Test + public void testVanillaRedirectUri() throws Exception { + String redirect = "https://example.com/authorize"; + Map params = new LinkedHashMap(); + params.put("foo", "bar"); + params.put("scope", "spam"); + testRedirectUri(redirect, params, redirect + "?foo=bar&scope=spam"); + } + + @Test + public void testTwoScopesRedirectUri() throws Exception { + String redirect = "https://example.com/authorize"; + Map params = new LinkedHashMap(); + params.put("foo", "bar"); + params.put("scope", "spam scope2"); + testRedirectUri(redirect, params, redirect + "?foo=bar&scope=spam%20scope2"); + } + + @Test + public void testRedirectUriWithUrlInParams() throws Exception { + String redirect = "https://example.com/authorize"; + Map params = Collections.singletonMap("redirect", + "https://foo/bar"); + testRedirectUri(redirect, params, redirect + "?redirect=https://foo/bar"); + } + + @Test + public void testRedirectUriWithQuery() throws Exception { + String redirect = "https://example.com/authorize?foo=bar"; + Map params = Collections.singletonMap("spam", + "bucket"); + testRedirectUri(redirect, params, redirect + "&spam=bucket"); + } + + public void testRedirectUri(String redirect, Map params, + String result) throws Exception { + OAuth2ClientContextFilter filter = new OAuth2ClientContextFilter(); + RedirectStrategy redirectStrategy = Mockito + .mock(RedirectStrategy.class); + filter.setRedirectStrategy(redirectStrategy); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + UserRedirectRequiredException exception = new UserRedirectRequiredException( + redirect, params); + filter.redirectUser(exception, request, response); + Mockito.verify(redirectStrategy) + .sendRedirect(request, response, result); + } + + @Test + public void testVanillaCurrentUri() throws Exception { + OAuth2ClientContextFilter filter = new OAuth2ClientContextFilter(); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setQueryString("foo=bar"); + assertEquals("http://localhost/?foo=bar", + filter.calculateCurrentUri(request)); + } + + @Test + public void testCurrentUriWithLegalSpaces() throws Exception { + OAuth2ClientContextFilter filter = new OAuth2ClientContextFilter(); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setQueryString("foo=bar%20spam"); + assertEquals("http://localhost/?foo=bar%20spam", + filter.calculateCurrentUri(request)); + } + + @Test + public void testCurrentUriWithNoQuery() throws Exception { + OAuth2ClientContextFilter filter = new OAuth2ClientContextFilter(); + MockHttpServletRequest request = new MockHttpServletRequest(); + assertEquals("http://localhost/", filter.calculateCurrentUri(request)); + } + + @Test + public void testCurrentUriWithIllegalSpaces() throws Exception { + OAuth2ClientContextFilter filter = new OAuth2ClientContextFilter(); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setQueryString("foo=bar+spam"); + assertEquals("http://localhost/?foo=bar+spam", + filter.calculateCurrentUri(request)); + } + + @Test + public void testCurrentUriRemovingCode() throws Exception { + OAuth2ClientContextFilter filter = new OAuth2ClientContextFilter(); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setQueryString("code=XXXX&foo=bar"); + assertEquals("http://localhost/?foo=bar", + filter.calculateCurrentUri(request)); + } + + @Test + public void testCurrentUriRemovingCodeInSecond() throws Exception { + OAuth2ClientContextFilter filter = new OAuth2ClientContextFilter(); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setQueryString("foo=bar&code=XXXX"); + assertEquals("http://localhost/?foo=bar", + filter.calculateCurrentUri(request)); + } + + @Test + public void testCurrentUriWithInvalidQueryString() throws Exception { + OAuth2ClientContextFilter filter = new OAuth2ClientContextFilter(); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setQueryString("foo=bar&code=XXXX&parm=%xx"); + try { + assertEquals(null, filter.calculateCurrentUri(request)); + } catch (IllegalStateException ex) { + // OAuth2ClientContextFilter.calculateCurrentUri() internally uses + // ServletUriComponentsBuilder.fromRequest(), which behaves differently in Spring Framework 5 + // and throws an IllegalStateException for a malformed URI. + // Previous to Spring Framework 5, 'null' would be returned by OAuth2ClientContextFilter.calculateCurrentUri() + // instead of the thrown IllegalStateException. + } + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/filter/TestOAuth2ClientAuthenticationProcessingFilter.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/filter/TestOAuth2ClientAuthenticationProcessingFilter.java deleted file mode 100644 index dbceebc30..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/filter/TestOAuth2ClientAuthenticationProcessingFilter.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2010-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.oauth2.client.filter; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import java.io.IOException; -import java.util.Arrays; - -import javax.servlet.ServletException; - -import org.junit.Test; -import org.mockito.Mockito; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.client.OAuth2RestOperations; -import org.springframework.security.oauth2.client.http.AccessTokenRequiredException; -import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; - -public class TestOAuth2ClientAuthenticationProcessingFilter { - - private OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter( - "/some/url"); - - private ResourceServerTokenServices tokenServices = Mockito.mock(ResourceServerTokenServices.class); - - private OAuth2RestOperations restTemplate = Mockito.mock(OAuth2RestOperations.class); - - private OAuth2Authentication authentication; - - @Test - public void testAuthentication() throws Exception { - filter.setRestTemplate(restTemplate); - filter.setTokenServices(tokenServices); - Mockito.when(restTemplate.getAccessToken()).thenReturn(new DefaultOAuth2AccessToken("FOO")); - this.authentication = new OAuth2Authentication(new DefaultAuthorizationRequest("client", Arrays.asList("read", - "write")), null); - Mockito.when(tokenServices.loadAuthentication("FOO")).thenReturn(authentication); - Authentication authentication = filter.attemptAuthentication(null, null); - assertEquals(this.authentication, authentication); - } - - @Test - public void testUnsuccessfulAuthentication() throws IOException, ServletException { - try { - filter.unsuccessfulAuthentication(null, null, new AccessTokenRequiredException("testing", null)); - fail("AccessTokenRedirectException must be thrown"); - } - catch (AccessTokenRequiredException ex) { - - } - } -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/filter/TestOAuth2ClientContextFilter.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/filter/TestOAuth2ClientContextFilter.java deleted file mode 100644 index ce9f9260c..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/filter/TestOAuth2ClientContextFilter.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.springframework.security.oauth2.client.filter; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -/** - * @author Ryan Heaton - */ -public class TestOAuth2ClientContextFilter { - - @Test - public void testVanillaCurrentUri() throws Exception { - OAuth2ClientContextFilter filter = new OAuth2ClientContextFilter(); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter("foo", "bar"); - assertEquals("http://localhost/?foo=bar", filter.calculateCurrentUri(request)); - } - - @Test - public void testCurrentUriRemovingCode() throws Exception { - OAuth2ClientContextFilter filter = new OAuth2ClientContextFilter(); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter("code", "XXXX"); - request.addParameter("foo", "bar"); - assertEquals("http://localhost/?foo=bar", filter.calculateCurrentUri(request)); - } - -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/http/OAuth2ErrorHandlerTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/http/OAuth2ErrorHandlerTests.java new file mode 100644 index 000000000..4f42c5dbf --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/http/OAuth2ErrorHandlerTests.java @@ -0,0 +1,315 @@ +/* + * Copyright 2002-2011 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.client.http; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.converter.HttpMessageConversionException; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; +import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException; +import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException; +import org.springframework.web.client.DefaultResponseErrorHandler; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.ResponseErrorHandler; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.when; + +/** + * @author Dave Syer + * @author Rob Winch + * + */ +@RunWith(MockitoJUnitRunner.class) +public class OAuth2ErrorHandlerTests { + + @Mock + private ClientHttpResponse response; + + @Rule + public ExpectedException expected = ExpectedException.none(); + + private BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails(); + + private final class TestClientHttpResponse implements ClientHttpResponse { + + private final HttpHeaders headers; + + private final HttpStatus status; + + private final InputStream body; + + public TestClientHttpResponse(HttpHeaders headers, int status) { + this(headers, status, new ByteArrayInputStream(new byte[0])); + } + + public TestClientHttpResponse(HttpHeaders headers, int status, InputStream bodyStream) { + this.headers = headers; + this.status = HttpStatus.valueOf(status); + this.body = bodyStream; + } + + public InputStream getBody() throws IOException { + return body; + } + + public HttpHeaders getHeaders() { + return headers; + } + + public HttpStatus getStatusCode() throws IOException { + return status; + } + + public String getStatusText() throws IOException { + return status.getReasonPhrase(); + } + + public int getRawStatusCode() throws IOException { + return status.value(); + } + + public void close() { + } + } + + private OAuth2ErrorHandler handler; + + @Before + public void setUp() throws Exception { + handler = new OAuth2ErrorHandler(resource); + + } + + /** + * test response with www-authenticate header + */ + @Test + public void testHandleErrorClientHttpResponse() throws Exception { + + HttpHeaders headers = new HttpHeaders(); + headers.set("www-authenticate", "Bearer error=foo"); + ClientHttpResponse response = new TestClientHttpResponse(headers, 401); + + // We lose the www-authenticate content in a nested exception (but it's still available) through the + // HttpClientErrorException + expected.expectMessage("401 Unauthorized"); + handler.handleError(response); + + } + + @Test + public void testHandleErrorWithInvalidToken() throws Exception { + + HttpHeaders headers = new HttpHeaders(); + headers.set("www-authenticate", "Bearer error=\"invalid_token\", description=\"foo\""); + ClientHttpResponse response = new TestClientHttpResponse(headers, 401); + + expected.expect(AccessTokenRequiredException.class); + expected.expectMessage("OAuth2 access denied"); + handler.handleError(response); + + } + + @Test + public void testCustomHandler() throws Exception { + + OAuth2ErrorHandler handler = new OAuth2ErrorHandler(new ResponseErrorHandler() { + + public boolean hasError(ClientHttpResponse response) throws IOException { + return true; + } + + public void handleError(ClientHttpResponse response) throws IOException { + throw new RuntimeException("planned"); + } + }, resource); + + HttpHeaders headers = new HttpHeaders(); + ClientHttpResponse response = new TestClientHttpResponse(headers, 401); + + expected.expectMessage("planned"); + handler.handleError(response); + + } + + @Test + public void testHandle500Error() throws Exception { + HttpHeaders headers = new HttpHeaders(); + ClientHttpResponse response = new TestClientHttpResponse(headers, 500); + + expected.expect(HttpServerErrorException.class); + handler.handleError(response); + } + + @Test + public void testHandleGeneric400Error() throws Exception { + HttpHeaders headers = new HttpHeaders(); + ClientHttpResponse response = new TestClientHttpResponse(headers, 400); + + expected.expect(HttpClientErrorException.class); + handler.handleError(response); + } + + @Test + public void testHandleGeneric403Error() throws Exception { + HttpHeaders headers = new HttpHeaders(); + ClientHttpResponse response = new TestClientHttpResponse(headers, 403); + + expected.expect(HttpClientErrorException.class); + handler.handleError(response); + } + + @Test + // See https://github.com/spring-projects/spring-security-oauth/issues/387 + public void testHandleGeneric403ErrorWithBody() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + ClientHttpResponse response = new TestClientHttpResponse(headers, 403, + new ByteArrayInputStream("{}".getBytes())); + handler = new OAuth2ErrorHandler(new DefaultResponseErrorHandler(), resource); + expected.expect(HttpClientErrorException.class); + handler.handleError(response); + } + + @Test + public void testBodyCanBeUsedByCustomHandler() throws Exception { + final String appSpecificBodyContent = "{\"some_status\":\"app error\"}"; + OAuth2ErrorHandler handler = new OAuth2ErrorHandler(new ResponseErrorHandler() { + public boolean hasError(ClientHttpResponse response) throws IOException { + return true; + } + + public void handleError(ClientHttpResponse response) throws IOException { + InputStream body = response.getBody(); + byte[] buf = new byte[appSpecificBodyContent.length()]; + int readResponse = body.read(buf); + Assert.assertEquals(buf.length, readResponse); + Assert.assertEquals(appSpecificBodyContent, new String(buf, "UTF-8")); + throw new RuntimeException("planned"); + } + }, resource); + HttpHeaders headers = new HttpHeaders(); + headers.set("Content-Length", "" + appSpecificBodyContent.length()); + headers.set("Content-Type", "application/json"); + InputStream appSpecificErrorBody = new ByteArrayInputStream(appSpecificBodyContent.getBytes("UTF-8")); + ClientHttpResponse response = new TestClientHttpResponse(headers, 400, appSpecificErrorBody); + + expected.expectMessage("planned"); + handler.handleError(response); + } + + @Test + public void testHandleErrorWithMissingHeader() throws IOException { + + final HttpHeaders headers = new HttpHeaders(); + when(response.getHeaders()).thenReturn(headers); + when(response.getStatusCode()).thenReturn(HttpStatus.BAD_REQUEST); + when(response.getBody()).thenReturn(new ByteArrayInputStream(new byte[0])); + when(response.getStatusText()).thenReturn(HttpStatus.BAD_REQUEST.toString()); + + expected.expect(HttpClientErrorException.class); + handler.handleError(response); + } + + // gh-875 + @Test + public void testHandleErrorWhenAccessDeniedMessageAndStatus400ThenThrowsUserDeniedAuthorizationException() throws Exception { + String accessDeniedMessage = "{\"error\":\"access_denied\", \"error_description\":\"some error message\"}"; + ByteArrayInputStream messageBody = new ByteArrayInputStream(accessDeniedMessage.getBytes()); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + ClientHttpResponse response = new TestClientHttpResponse(headers, 400, messageBody); + expected.expect(UserDeniedAuthorizationException.class); + handler.handleError(response); + } + + // gh-875 + @Test + public void testHandleErrorWhenAccessDeniedMessageAndStatus403ThenThrowsOAuth2AccessDeniedException() throws Exception { + String accessDeniedMessage = "{\"error\":\"access_denied\", \"error_description\":\"some error message\"}"; + ByteArrayInputStream messageBody = new ByteArrayInputStream(accessDeniedMessage.getBytes()); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + ClientHttpResponse response = new TestClientHttpResponse(headers, 403, messageBody); + expected.expect(OAuth2AccessDeniedException.class); + handler.handleError(response); + } + + @Test + public void testHandleMessageConversionExceptions() throws Exception { + HttpMessageConverter extractor = new HttpMessageConverter() { + @Override + public boolean canRead(Class clazz, MediaType mediaType) { + return true; + } + + @Override + public boolean canWrite(Class clazz, MediaType mediaType) { + return false; + } + + @Override + public List getSupportedMediaTypes() { + return null; + } + + @Override + public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { + throw new HttpMessageConversionException("error"); + } + + @Override + public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { + + } + }; + + ArrayList> messageConverters = new ArrayList>(); + messageConverters.add(extractor); + handler.setMessageConverters(messageConverters); + + HttpHeaders headers = new HttpHeaders(); + final String appSpecificBodyContent = "This user is not authorized"; + InputStream appSpecificErrorBody = new ByteArrayInputStream(appSpecificBodyContent.getBytes("UTF-8")); + ClientHttpResponse response = new TestClientHttpResponse(headers, 401, appSpecificErrorBody); + + expected.expect(HttpClientErrorException.class); + handler.handleError(response); + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/http/TestOAuth2ErrorHandler.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/http/TestOAuth2ErrorHandler.java deleted file mode 100644 index 2c9329692..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/http/TestOAuth2ErrorHandler.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2002-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.client.http; - -import static org.mockito.Mockito.when; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.ResponseErrorHandler; - -/** - * @author Dave Syer - * @author Rob Winch - * - */ -@RunWith(MockitoJUnitRunner.class) -public class TestOAuth2ErrorHandler { - - @Mock - private ClientHttpResponse response; - - @Rule - public ExpectedException expected = ExpectedException.none(); - - private BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails(); - - private final class TestClientHttpResponse implements ClientHttpResponse { - - private final HttpHeaders headers; - - public TestClientHttpResponse(HttpHeaders headers) { - this.headers = headers; - - } - - public InputStream getBody() throws IOException { - return null; - } - - public HttpHeaders getHeaders() { - return headers; - } - - public HttpStatus getStatusCode() throws IOException { - return null; - } - - public String getStatusText() throws IOException { - return null; - } - - public int getRawStatusCode() throws IOException { - return 0; - } - - public void close() { - } - } - - private OAuth2ErrorHandler handler = new OAuth2ErrorHandler(resource); - - /** - * test response with www-authenticate header - */ - @Test - public void testHandleErrorClientHttpResponse() throws Exception { - - HttpHeaders headers = new HttpHeaders(); - headers.set("www-authenticate", "Bearer error=foo"); - ClientHttpResponse response = new TestClientHttpResponse(headers); - - expected.expectMessage("foo"); - handler.handleError(response); - - } - - @Test - public void testHandleErrorWithInvalidToken() throws Exception { - - HttpHeaders headers = new HttpHeaders(); - headers.set("www-authenticate", "Bearer error=\"invalid_token\", description=\"foo\""); - ClientHttpResponse response = new TestClientHttpResponse(headers); - - expected.expect(AccessTokenRequiredException.class); - expected.expectMessage("OAuth2 access denied"); - handler.handleError(response); - - } - - @Test - public void testCustomHandler() throws Exception { - - OAuth2ErrorHandler handler = new OAuth2ErrorHandler(new ResponseErrorHandler() { - - public boolean hasError(ClientHttpResponse response) throws IOException { - return true; - } - - public void handleError(ClientHttpResponse response) throws IOException { - throw new RuntimeException("planned"); - } - }, resource); - - HttpHeaders headers = new HttpHeaders(); - ClientHttpResponse response = new TestClientHttpResponse(headers); - - expected.expectMessage("planned"); - handler.handleError(response); - - } - - @Test - public void testHandleErrorWithMissingHeader() throws IOException { - - final HttpHeaders headers = new HttpHeaders(); - when(response.getHeaders()).thenReturn(headers); - when(response.getStatusCode()).thenReturn(HttpStatus.BAD_REQUEST); - when(response.getBody()).thenReturn(new ByteArrayInputStream(new byte[0])); - when(response.getStatusText()).thenReturn(HttpStatus.BAD_REQUEST.toString()); - - expected.expect(HttpClientErrorException.class); - handler.handleError(response); - } -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/TestAccessTokenProviderChain.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/AccessTokenProviderChainTests.java similarity index 50% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/TestAccessTokenProviderChain.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/AccessTokenProviderChainTests.java index 7c1d49e8c..8848ecf0b 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/TestAccessTokenProviderChain.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/AccessTokenProviderChainTests.java @@ -1,10 +1,10 @@ /* - * Copyright 2006-2011 the original author or authors. + * Copyright 2006-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 * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -14,8 +14,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import java.util.Arrays; +import java.util.Calendar; import java.util.Collections; import java.util.Date; @@ -31,6 +37,9 @@ import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; +import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; @@ -43,7 +52,7 @@ * @author Dave Syer * */ -public class TestAccessTokenProviderChain { +public class AccessTokenProviderChainTests { private BaseOAuth2ProtectedResourceDetails resource; @@ -56,7 +65,7 @@ public class TestAccessTokenProviderChain { private ClientTokenServices clientTokenServices = Mockito.mock(ClientTokenServices.class); - public TestAccessTokenProviderChain() { + public AccessTokenProviderChainTests() { resource = new BaseOAuth2ProtectedResourceDetails(); resource.setId("resource"); } @@ -78,7 +87,7 @@ public void testSunnyDay() throws Exception { @Test public void testSunnyDayWithTokenServicesGet() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Collections. emptyList()); - Mockito.when(clientTokenServices.getAccessToken(resource, user)).thenReturn(accessToken); + when(clientTokenServices.getAccessToken(resource, user)).thenReturn(accessToken); chain.setClientTokenServices(clientTokenServices); AccessTokenRequest request = new DefaultAccessTokenRequest(); SecurityContextHolder.getContext().setAuthentication(user); @@ -98,6 +107,18 @@ public void testSunnyDayWithTokenServicesSave() throws Exception { Mockito.verify(clientTokenServices).saveAccessToken(resource, user, token); } + @Test + public void testSunnyDayClientCredentialsWithTokenServicesSave() throws Exception { + AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); + chain.setClientTokenServices(clientTokenServices); + AccessTokenRequest request = new DefaultAccessTokenRequest(); + resource = new ClientCredentialsResourceDetails(); + resource.setId("resource"); + OAuth2AccessToken token = chain.obtainAccessToken(resource, request); + assertNotNull(token); + Mockito.verify(clientTokenServices).saveAccessToken(resource, null, token); + } + @Test public void testSunnyDayWithExpiredToken() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); @@ -114,7 +135,7 @@ public void testSunnyDayWithExpiredTokenAndTokenServices() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); chain.setClientTokenServices(clientTokenServices); accessToken.setExpiration(new Date(System.currentTimeMillis() - 1000)); - Mockito.when(clientTokenServices.getAccessToken(resource, user)).thenReturn(accessToken); + when(clientTokenServices.getAccessToken(resource, user)).thenReturn(accessToken); AccessTokenRequest request = new DefaultAccessTokenRequest(); SecurityContextHolder.getContext().setAuthentication(user); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); @@ -124,7 +145,7 @@ public void testSunnyDayWithExpiredTokenAndTokenServices() throws Exception { } @Test - public void testSunnyDayWIthExpiredTokenAndValidRefreshToken() throws Exception { + public void testSunnyDayWithExpiredTokenAndValidRefreshToken() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); accessToken.setExpiration(new Date(System.currentTimeMillis() - 1000)); accessToken.setRefreshToken(new DefaultOAuth2RefreshToken("EXP")); @@ -133,20 +154,65 @@ public void testSunnyDayWIthExpiredTokenAndValidRefreshToken() throws Exception SecurityContextHolder.getContext().setAuthentication(user); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertNotNull(token); + assertEquals(refreshedToken, token); + } + + @Test + public void testSunnyDayWithTokenWithinClockSkewWindowAndValidRefreshToken() throws Exception { + AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); + accessToken.setExpiration(new Date(System.currentTimeMillis() + 1000)); + accessToken.setRefreshToken(new DefaultOAuth2RefreshToken("EXP")); + AccessTokenRequest request = new DefaultAccessTokenRequest(); + request.setExistingToken(accessToken); + SecurityContextHolder.getContext().setAuthentication(user); + OAuth2AccessToken token = chain.obtainAccessToken(resource, request); + assertNotNull(token); + assertEquals(refreshedToken, token); + } + + @Test + public void testSunnyDayWithTokenOutsideClockSkewWindowAndValidRefreshToken() throws Exception { + AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); + accessToken.setExpiration(new Date(System.currentTimeMillis() + 31000)); + accessToken.setRefreshToken(new DefaultOAuth2RefreshToken("EXP")); + AccessTokenRequest request = new DefaultAccessTokenRequest(); + request.setExistingToken(accessToken); + SecurityContextHolder.getContext().setAuthentication(user); + OAuth2AccessToken token = chain.obtainAccessToken(resource, request); + assertNotNull(token); + assertEquals(accessToken, token); } @Test(expected = InvalidTokenException.class) - public void testSunnyDayWIthExpiredTokenAndExpiredRefreshToken() throws Exception { + public void testSunnyDayWithExpiredTokenAndExpiredRefreshToken() throws Exception { + AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); + accessToken.setExpiration(new Date(System.currentTimeMillis() - 1000)); + DefaultOAuth2RefreshToken refreshToken = new DefaultExpiringOAuth2RefreshToken("EXP", + new Date(System.currentTimeMillis() - 1000)); + accessToken.setRefreshToken(refreshToken); + AccessTokenRequest request = new DefaultAccessTokenRequest(); + request.setExistingToken(accessToken); + SecurityContextHolder.getContext().setAuthentication(user); + OAuth2AccessToken token = chain.obtainAccessToken(resource, request); + assertNotNull(token); + } + + @Test + public void testSunnyDayWithExpiredTokenAndExpiredRefreshTokenForClientCredentialsResource() { + resource = new ClientCredentialsResourceDetails(); + resource.setId("resource"); AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); accessToken.setExpiration(new Date(System.currentTimeMillis() - 1000)); - DefaultOAuth2RefreshToken refreshToken = new DefaultExpiringOAuth2RefreshToken("EXP", new Date( - System.currentTimeMillis() - 1000)); + DefaultOAuth2RefreshToken refreshToken = new DefaultExpiringOAuth2RefreshToken("EXP", + new Date(System.currentTimeMillis() - 1000)); accessToken.setRefreshToken(refreshToken); AccessTokenRequest request = new DefaultAccessTokenRequest(); request.setExistingToken(accessToken); SecurityContextHolder.getContext().setAuthentication(user); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertNotNull(token); + assertSame(accessToken, token); + assertSame(refreshToken, token.getRefreshToken()); } @Test @@ -155,14 +221,15 @@ public void testMissingSecurityContext() throws Exception { AccessTokenRequest request = new DefaultAccessTokenRequest(); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertNotNull(token); - // If there is no authentication to store it with a token is still acquired if possible + // If there is no authentication to store it with a token is still acquired if + // possible } @Test(expected = InsufficientAuthenticationException.class) public void testAnonymousUser() throws Exception { AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(new StubAccessTokenProvider())); - SecurityContextHolder.getContext().setAuthentication( - new AnonymousAuthenticationToken("foo", "bar", user.getAuthorities())); + SecurityContextHolder.getContext() + .setAuthentication(new AnonymousAuthenticationToken("foo", "bar", user.getAuthorities())); AccessTokenRequest request = new DefaultAccessTokenRequest(); OAuth2AccessToken token = chain.obtainAccessToken(resource, request); assertNotNull(token); @@ -182,9 +249,100 @@ public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails detail assertNotNull(token); } + @Test + public void testRefreshAccessTokenReplacingNullValue() throws Exception { + DefaultOAuth2AccessToken accessToken = getExpiredToken(); + DefaultOAuth2AccessToken refreshedAccessToken = new DefaultOAuth2AccessToken("refreshed-access-token"); + AccessTokenProviderChain chain = getTokenProvider(accessToken, refreshedAccessToken); + + SecurityContextHolder.getContext().setAuthentication(user); + + // Obtain a new Access Token + AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); + AccessTokenRequest request = new DefaultAccessTokenRequest(); + OAuth2AccessToken newAccessToken = chain.refreshAccessToken(resource, accessToken.getRefreshToken(), request); + // gh-712 + assertEquals(newAccessToken.getRefreshToken(), accessToken.getRefreshToken()); + } + + @Test + public void testRefreshAccessTokenKeepingOldValue() throws Exception { + DefaultOAuth2AccessToken accessToken = getExpiredToken(); + DefaultOAuth2AccessToken refreshedAccessToken = new DefaultOAuth2AccessToken("refreshed-access-token"); + refreshedAccessToken.setRefreshToken(new DefaultOAuth2RefreshToken("other-refresh-token")); + AccessTokenProviderChain chain = getTokenProvider(accessToken, refreshedAccessToken); + + SecurityContextHolder.getContext().setAuthentication(user); + + // Obtain a new Access Token + AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); + AccessTokenRequest request = new DefaultAccessTokenRequest(); + OAuth2AccessToken newAccessToken = chain.refreshAccessToken(resource, accessToken.getRefreshToken(), request); + // gh-816 + assertEquals(newAccessToken.getRefreshToken(), refreshedAccessToken.getRefreshToken()); + } + + private DefaultOAuth2AccessToken getExpiredToken() { + Calendar tokenExpiry = Calendar.getInstance(); + DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("access-token"); + accessToken.setExpiration(tokenExpiry.getTime()); + accessToken.setRefreshToken(new DefaultOAuth2RefreshToken("refresh-token")); + return accessToken; + } + + // gh-712 + @Test + public void testRefreshAccessTokenTwicePreserveRefreshToken() throws Exception { + DefaultOAuth2AccessToken accessToken = getExpiredToken(); + DefaultOAuth2AccessToken expectedRefreshedAccessToken = new DefaultOAuth2AccessToken("refreshed-access-token"); + expectedRefreshedAccessToken.setExpiration(accessToken.getExpiration()); + + AccessTokenProviderChain chain = getTokenProvider(accessToken, expectedRefreshedAccessToken); + + SecurityContextHolder.getContext().setAuthentication(user); + + // Obtain a new Access Token + AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); + AccessTokenRequest request = new DefaultAccessTokenRequest(); + OAuth2AccessToken tokenResult = chain.obtainAccessToken(resource, request); + assertEquals(accessToken, tokenResult); + + // Obtain the 1st Refreshed Access Token + Calendar tokenExpiry = Calendar.getInstance(); + tokenExpiry.setTime(tokenResult.getExpiration()); + tokenExpiry.add(Calendar.MINUTE, -1); + DefaultOAuth2AccessToken.class.cast(tokenResult).setExpiration(tokenExpiry.getTime()); // Expire + request = new DefaultAccessTokenRequest(); + request.setExistingToken(tokenResult); + tokenResult = chain.obtainAccessToken(resource, request); + assertEquals(expectedRefreshedAccessToken, tokenResult); + + // Obtain the 2nd Refreshed Access Token + tokenExpiry.setTime(tokenResult.getExpiration()); + tokenExpiry.add(Calendar.MINUTE, -1); + DefaultOAuth2AccessToken.class.cast(tokenResult).setExpiration(tokenExpiry.getTime()); // Expire + request = new DefaultAccessTokenRequest(); + request.setExistingToken(tokenResult); + tokenResult = chain.obtainAccessToken(resource, request); + assertEquals(expectedRefreshedAccessToken, tokenResult); + } + + private AccessTokenProviderChain getTokenProvider(DefaultOAuth2AccessToken accessToken, + DefaultOAuth2AccessToken refreshedAccessToken) { + AccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider(); + accessTokenProvider = spy(accessTokenProvider); + doReturn(accessToken).when(accessTokenProvider).obtainAccessToken(any(OAuth2ProtectedResourceDetails.class), + any(AccessTokenRequest.class)); + doReturn(refreshedAccessToken).when(accessTokenProvider).refreshAccessToken( + any(OAuth2ProtectedResourceDetails.class), any(OAuth2RefreshToken.class), + any(AccessTokenRequest.class)); + AccessTokenProviderChain chain = new AccessTokenProviderChain(Arrays.asList(accessTokenProvider)); + return chain; + } + private class StubAccessTokenProvider implements AccessTokenProvider { - public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest parameters) - throws UserRedirectRequiredException, AccessDeniedException { + public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, + AccessTokenRequest parameters) throws UserRedirectRequiredException, AccessDeniedException { return accessToken; } @@ -195,7 +353,8 @@ public boolean supportsRefresh(OAuth2ProtectedResourceDetails resource) { public OAuth2AccessToken refreshAccessToken(OAuth2ProtectedResourceDetails resource, OAuth2RefreshToken refreshToken, AccessTokenRequest request) throws UserRedirectRequiredException { if (refreshToken instanceof ExpiringOAuth2RefreshToken) { - if (((ExpiringOAuth2RefreshToken) refreshToken).getExpiration().getTime() < System.currentTimeMillis()) { + if (((ExpiringOAuth2RefreshToken) refreshToken).getExpiration().getTime() < System + .currentTimeMillis()) { // this is what a real provider would do (re-throw a remote exception) throw new InvalidTokenException("Expired refresh token"); } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/JdbcClientTokenServicesTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/JdbcClientTokenServicesTests.java new file mode 100644 index 000000000..2195f326a --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/JdbcClientTokenServicesTests.java @@ -0,0 +1,141 @@ +package org.springframework.security.oauth2.client.token; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.company.oauth2.CustomOAuth2AccessToken; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.util.SerializationStrategy; +import org.springframework.security.oauth2.common.util.SerializationUtils; +import org.springframework.security.oauth2.common.util.WhitelistedSerializationStrategy; + +import static org.junit.Assert.*; + +/** + * @author Dave Syer + * @author Artem Smotrakov + */ +public class JdbcClientTokenServicesTests { + + private JdbcClientTokenServices tokenStore; + + private EmbeddedDatabase db; + + public JdbcClientTokenServices getTokenServices() { + return tokenStore; + } + + @Before + public void setUp() throws Exception { + // creates a HSQL in-memory db populated from default scripts classpath:schema.sql and classpath:data.sql + db = new EmbeddedDatabaseBuilder().addDefaultScripts().build(); + tokenStore = new JdbcClientTokenServices(db); + } + + @After + public void tearDown() throws Exception { + db.shutdown(); + } + + @Test + public void testSaveAndRetrieveToken() throws Exception { + OAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("FOO"); + Authentication authentication = new UsernamePasswordAuthenticationToken("marissa", "koala"); + AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); + resource.setClientId("client"); + resource.setScope(Arrays.asList("foo", "bar")); + tokenStore.saveAccessToken(resource, authentication, accessToken); + OAuth2AccessToken result = tokenStore.getAccessToken(resource, authentication); + assertEquals(accessToken, result); + } + + @Test + public void testSaveAndRetrieveTokenForClientCredentials() throws Exception { + OAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("FOO"); + AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); + resource.setClientId("client"); + resource.setScope(Arrays.asList("foo", "bar")); + tokenStore.saveAccessToken(resource, null, accessToken); + OAuth2AccessToken result = tokenStore.getAccessToken(resource, null); + assertEquals(accessToken, result); + } + + @Test + public void testSaveAndRemoveToken() throws Exception { + OAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("FOO"); + Authentication authentication = new UsernamePasswordAuthenticationToken("marissa", "koala"); + AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); + resource.setClientId("client"); + resource.setScope(Arrays.asList("foo", "bar")); + tokenStore.saveAccessToken(resource, authentication, accessToken); + tokenStore.removeAccessToken(resource, authentication); + // System.err.println(new JdbcTemplate(db).queryForList("select * from oauth_client_token")); + OAuth2AccessToken result = tokenStore.getAccessToken(resource, authentication); + assertNull(result); + } + + @Test + public void testSaveAndRetrieveCustomToken() { + OAuth2AccessToken accessToken = new CustomOAuth2AccessToken("FOO"); + Authentication authentication = new UsernamePasswordAuthenticationToken("marissa", "koala"); + AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); + resource.setClientId("client"); + resource.setScope(Arrays.asList("foo", "bar")); + tokenStore.saveAccessToken(resource, authentication, accessToken); + OAuth2AccessToken result = tokenStore.getAccessToken(resource, authentication); + assertNotNull(result); + assertEquals(accessToken, result); + } + + @Test(expected = IllegalArgumentException.class) + public void testSaveAndRetrieveNotAllowedCustomToken() { + OAuth2AccessToken accessToken = new CustomOAuth2AccessToken("FOO"); + Authentication authentication = new UsernamePasswordAuthenticationToken("marissa", "koala"); + AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); + resource.setClientId("client"); + resource.setScope(Arrays.asList("foo", "bar")); + WhitelistedSerializationStrategy newStrategy = new WhitelistedSerializationStrategy(); + SerializationStrategy oldStrategy = SerializationUtils.getSerializationStrategy(); + try { + SerializationUtils.setSerializationStrategy(newStrategy); + tokenStore.saveAccessToken(resource, authentication, accessToken); + tokenStore.getAccessToken(resource, authentication); + } finally { + SerializationUtils.setSerializationStrategy(oldStrategy); + } + } + + @Test + public void testSaveAndRetrieveCustomTokenWithCustomSerializationStrategy() { + List allowedClasses = new ArrayList(); + allowedClasses.add("java.util."); + allowedClasses.add("org.springframework.security."); + allowedClasses.add("org.company.oauth2.CustomOAuth2AccessToken"); + WhitelistedSerializationStrategy newStrategy = new WhitelistedSerializationStrategy(allowedClasses); + SerializationStrategy oldStrategy = SerializationUtils.getSerializationStrategy(); + try { + SerializationUtils.setSerializationStrategy(newStrategy); + OAuth2AccessToken accessToken = new CustomOAuth2AccessToken("FOO"); + Authentication authentication = new UsernamePasswordAuthenticationToken("marissa", "koala"); + AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); + resource.setClientId("client"); + resource.setScope(Arrays.asList("foo", "bar")); + tokenStore.saveAccessToken(resource, authentication, accessToken); + OAuth2AccessToken result = tokenStore.getAccessToken(resource, authentication); + assertNotNull(result); + assertEquals(accessToken, result); + } finally { + SerializationUtils.setSerializationStrategy(oldStrategy); + } + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/TestOAuth2AccessTokenSupport.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/OAuth2AccessTokenSupportTests.java similarity index 69% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/TestOAuth2AccessTokenSupport.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/OAuth2AccessTokenSupportTests.java index 6a085d009..c5a0c66af 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/TestOAuth2AccessTokenSupport.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/OAuth2AccessTokenSupportTests.java @@ -1,20 +1,21 @@ /* - * Cloud Foundry 2012.02.03 Beta - * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. + * Copyright 2012-2013 the original author or authors. * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. + * 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.client.token; -import static org.junit.Assert.assertEquals; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -23,9 +24,10 @@ import java.net.URI; import java.util.Arrays; -import org.codehaus.jackson.map.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -42,11 +44,13 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import static org.junit.Assert.assertEquals; + /** * @author Dave Syer * */ -public class TestOAuth2AccessTokenSupport { +public class OAuth2AccessTokenSupportTests { private ClientCredentialsResourceDetails resource = new ClientCredentialsResourceDetails(); @@ -59,6 +63,8 @@ public class TestOAuth2AccessTokenSupport { private IOException error; private DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("FOO"); + + private AccessTokenRequest request = new DefaultAccessTokenRequest(); private ObjectMapper objectMapper = new ObjectMapper(); @@ -68,7 +74,7 @@ public class TestOAuth2AccessTokenSupport { public void init() throws Exception { resource.setClientId("client"); resource.setClientSecret("secret"); - resource.setAccessTokenUri("http://nowhere/token"); + resource.setAccessTokenUri("https://nowhere/token"); response = new StubHttpClientResponse(); support.setRequestFactory(new ClientHttpRequestFactory() { public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { @@ -81,13 +87,13 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO public void testRetrieveTokenFailsWhenTokenEndpointNotAvailable() { error = new IOException("Planned"); response.setStatus(HttpStatus.BAD_REQUEST); - support.retrieveToken(form, requestHeaders, resource); + support.retrieveToken(request, resource, form, requestHeaders); } @Test public void testRetrieveToken() throws Exception { response.setBody(objectMapper.writeValueAsString(accessToken)); - OAuth2AccessToken retrieveToken = support.retrieveToken(form, requestHeaders, resource); + OAuth2AccessToken retrieveToken = support.retrieveToken(request, resource, form, requestHeaders); assertEquals(accessToken, retrieveToken); } @@ -99,7 +105,28 @@ public void testRetrieveTokenFormEncoded() throws Exception { responseHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); response.setBody("access_token=FOO"); response.setHeaders(responseHeaders ); - OAuth2AccessToken retrieveToken = support.retrieveToken(form, requestHeaders, resource); + OAuth2AccessToken retrieveToken = support.retrieveToken(request, resource, form, requestHeaders); + assertEquals(accessToken, retrieveToken); + } + + @Test + public void testRequestEnhanced() throws Exception { + DefaultRequestEnhancer enhancer = new DefaultRequestEnhancer(); + enhancer.setParameterIncludes(Arrays.asList("foo")); + request.set("foo", "bar"); + support.setTokenRequestEnhancer(enhancer); + response.setBody(objectMapper.writeValueAsString(accessToken)); + OAuth2AccessToken retrieveToken = support.retrieveToken(request, resource, form, requestHeaders); + assertEquals("[bar]", form.get("foo").toString()); + assertEquals(accessToken, retrieveToken); + } + + @Test + public void testRequestNotEnhanced() throws Exception { + request.set("foo", "bar"); + response.setBody(objectMapper.writeValueAsString(accessToken)); + OAuth2AccessToken retrieveToken = support.retrieveToken(request, resource, form, requestHeaders); + assertEquals(null, form.get("foo")); assertEquals(accessToken, retrieveToken); } @@ -161,6 +188,10 @@ public HttpMethod getMethod() { return HttpMethod.GET; } + public String getMethodValue() { + return getMethod().name(); + } + public URI getURI() { return null; } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/TestJdbcClientTokenServices.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/TestJdbcClientTokenServices.java deleted file mode 100644 index 45ac99881..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/TestJdbcClientTokenServices.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.springframework.security.oauth2.client.token; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import java.util.Arrays; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; -import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2AccessToken; - -/** - * @author Dave Syer - * - */ -public class TestJdbcClientTokenServices { - - private JdbcClientTokenServices tokenStore; - - private EmbeddedDatabase db; - - public JdbcClientTokenServices getTokenServices() { - return tokenStore; - } - - @Before - public void setUp() throws Exception { - // creates a HSQL in-memory db populated from default scripts classpath:schema.sql and classpath:data.sql - db = new EmbeddedDatabaseBuilder().addDefaultScripts().build(); - tokenStore = new JdbcClientTokenServices(db); - } - - @After - public void tearDown() throws Exception { - db.shutdown(); - } - - @Test - public void testSaveAndRetrieveToken() throws Exception { - OAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("FOO"); - Authentication authentication = new UsernamePasswordAuthenticationToken("marissa", "koala"); - AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); - resource.setClientId("client"); - resource.setScope(Arrays.asList("foo", "bar")); - tokenStore.saveAccessToken(resource, authentication, accessToken); - OAuth2AccessToken result = tokenStore.getAccessToken(resource, authentication); - assertEquals(accessToken, result); - } - - @Test - public void testSaveAndRemoveToken() throws Exception { - OAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("FOO"); - Authentication authentication = new UsernamePasswordAuthenticationToken("marissa", "koala"); - AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); - resource.setClientId("client"); - resource.setScope(Arrays.asList("foo", "bar")); - tokenStore.saveAccessToken(resource, authentication, accessToken); - tokenStore.removeAccessToken(resource, authentication); - // System.err.println(new JdbcTemplate(db).queryForList("select * from oauth_client_token")); - OAuth2AccessToken result = tokenStore.getAccessToken(resource, authentication); - assertNull(result); - } - -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/TestAuthorizationCodeAccessTokenProvider.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeAccessTokenProviderTests.java similarity index 77% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/TestAuthorizationCodeAccessTokenProvider.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeAccessTokenProviderTests.java index 7190b98fd..26ac35ae3 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/TestAuthorizationCodeAccessTokenProvider.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeAccessTokenProviderTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -25,6 +25,7 @@ import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -32,7 +33,7 @@ * @author Dave Syer * */ -public class TestAuthorizationCodeAccessTokenProvider { +public class AuthorizationCodeAccessTokenProviderTests { @Rule public ExpectedException expected = ExpectedException.none(); @@ -41,8 +42,8 @@ public class TestAuthorizationCodeAccessTokenProvider { private AuthorizationCodeAccessTokenProvider provider = new AuthorizationCodeAccessTokenProvider() { @Override - protected OAuth2AccessToken retrieveToken(MultiValueMap form, HttpHeaders headers, - OAuth2ProtectedResourceDetails resource) { + protected OAuth2AccessToken retrieveToken(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, + MultiValueMap form, HttpHeaders headers) { params.putAll(form); return new DefaultOAuth2AccessToken("FOO"); } @@ -52,9 +53,19 @@ protected OAuth2AccessToken retrieveToken(MultiValueMap form, Ht @Test public void testGetAccessToken() throws Exception { + AccessTokenRequest request = new DefaultAccessTokenRequest(); + request.setAuthorizationCode("foo"); + request.setPreservedState(new Object()); + resource.setAccessTokenUri("http://localhost/oauth/token"); + assertEquals("FOO", provider.obtainAccessToken(resource, request).getValue()); + } + + @Test + public void testGetAccessTokenFailsWithNoState() throws Exception { AccessTokenRequest request = new DefaultAccessTokenRequest(); request.setAuthorizationCode("foo"); resource.setAccessTokenUri("http://localhost/oauth/token"); + expected.expect(InvalidRequestException.class); assertEquals("FOO", provider.obtainAccessToken(resource, request).getValue()); } @@ -73,7 +84,8 @@ public void testRedirectToAuthorizationEndpoint() throws Exception { } } - @Test(expected = IllegalStateException.class) + // A missing redirect just means the server has to deal with it + @Test(expected = UserRedirectRequiredException.class) public void testRedirectNotSpecified() throws Exception { AccessTokenRequest request = new DefaultAccessTokenRequest(); resource.setUserAuthorizationUri("http://localhost/oauth/authorize"); @@ -87,12 +99,12 @@ public void testGetAccessTokenRequest() throws Exception { request.setStateKey("bar"); request.setPreservedState(new Object()); resource.setAccessTokenUri("http://localhost/oauth/token"); - resource.setPreEstablishedRedirectUri("http://anywhere.com/"); + resource.setPreEstablishedRedirectUri("https://anywhere.com/"); assertEquals("FOO", provider.obtainAccessToken(resource, request).getValue()); // System.err.println(params); assertEquals("authorization_code", params.getFirst("grant_type")); assertEquals("foo", params.getFirst("code")); - assertEquals("http://anywhere.com/", params.getFirst("redirect_uri")); + assertEquals("https://anywhere.com/", params.getFirst("redirect_uri")); // State is not set in token request assertEquals(null, params.getFirst("state")); } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/TestAuthorizationCodeAccessTokenProviderWithConversion.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeAccessTokenProviderWithConversionTests.java similarity index 94% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/TestAuthorizationCodeAccessTokenProviderWithConversion.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeAccessTokenProviderWithConversionTests.java index a83e7f13f..61a645551 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/TestAuthorizationCodeAccessTokenProviderWithConversion.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeAccessTokenProviderWithConversionTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -12,9 +12,6 @@ */ package org.springframework.security.oauth2.client.token.grant.code; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.junit.Assert.assertEquals; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -23,13 +20,14 @@ import java.net.URI; import java.net.URISyntaxException; -import org.codehaus.jackson.map.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper; import org.hamcrest.Description; import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; import org.junit.Rule; import org.junit.Test; -import org.junit.internal.matchers.TypeSafeMatcher; import org.junit.rules.ExpectedException; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -44,11 +42,14 @@ import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.InvalidClientException; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertEquals; + /** * @author Dave Syer * */ -public class TestAuthorizationCodeAccessTokenProviderWithConversion { +public class AuthorizationCodeAccessTokenProviderWithConversionTests { private static class StubClientHttpRequest implements ClientHttpRequest { @@ -92,7 +93,7 @@ public HttpHeaders getHeaders() { public URI getURI() { try { - return new URI("http://foo.com/"); + return new URI("https://www.foo.com/"); } catch (URISyntaxException e) { throw new IllegalStateException(e); @@ -103,6 +104,10 @@ public HttpMethod getMethod() { return HttpMethod.POST; } + public String getMethodValue() { + return getMethod().name(); + } + public ClientHttpResponse execute() throws IOException { return new ClientHttpResponse() { @@ -156,6 +161,7 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO AccessTokenRequest request = new DefaultAccessTokenRequest(); request.setAuthorizationCode("foo"); resource.setAccessTokenUri("http://localhost/oauth/token"); + request.setPreservedState(new Object()); setUpRestTemplate(); assertEquals(token, provider.obtainAccessToken(resource, request)); } @@ -171,6 +177,7 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO }; AccessTokenRequest request = new DefaultAccessTokenRequest(); request.setAuthorizationCode("foo"); + request.setPreservedState(new Object()); resource.setAccessTokenUri("http://localhost/oauth/token"); expected.expect(OAuth2AccessDeniedException.class); expected.expect(hasCause(instanceOf(InvalidClientException.class))); @@ -190,6 +197,7 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO }; AccessTokenRequest request = new DefaultAccessTokenRequest(); request.setAuthorizationCode("foo"); + request.setPreservedState(new Object()); resource.setAccessTokenUri("http://localhost/oauth/token"); setUpRestTemplate(); assertEquals(token, provider.obtainAccessToken(resource, request)); @@ -207,6 +215,7 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO }; AccessTokenRequest request = new DefaultAccessTokenRequest(); request.setAuthorizationCode("foo"); + request.setPreservedState(new Object()); resource.setAccessTokenUri("http://localhost/oauth/token"); expected.expect(OAuth2AccessDeniedException.class); expected.expect(hasCause(instanceOf(InvalidClientException.class))); diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/TestAuthorizationCodeResourceDetails.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeResourceDetailsTests.java similarity index 72% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/TestAuthorizationCodeResourceDetails.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeResourceDetailsTests.java index b08eeec98..cc17ff426 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/TestAuthorizationCodeResourceDetails.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/code/AuthorizationCodeResourceDetailsTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -23,25 +23,25 @@ * @author Dave Syer * */ -public class TestAuthorizationCodeResourceDetails { +public class AuthorizationCodeResourceDetailsTests { private AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); @Test public void testGetDefaultRedirectUri() { - details.setPreEstablishedRedirectUri("http://anywhere.com/"); + details.setPreEstablishedRedirectUri("https://anywhere.com/"); DefaultAccessTokenRequest request = new DefaultAccessTokenRequest(); - request.setCurrentUri("http://nowhere.com/"); - assertEquals("http://nowhere.com/", details.getRedirectUri(request)); + request.setCurrentUri("https://nowhere.com/"); + assertEquals("https://nowhere.com/", details.getRedirectUri(request)); } @Test public void testGetOverrideRedirectUri() { - details.setPreEstablishedRedirectUri("http://anywhere.com/"); + details.setPreEstablishedRedirectUri("https://anywhere.com/"); details.setUseCurrentUri(false); DefaultAccessTokenRequest request = new DefaultAccessTokenRequest(); - request.setCurrentUri("http://nowhere.com/"); - assertEquals("http://anywhere.com/", details.getRedirectUri(request)); + request.setCurrentUri("https://nowhere.com/"); + assertEquals("https://anywhere.com/", details.getRedirectUri(request)); } } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/implicit/TestImplicitAccessTokenProvider.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/implicit/ImplicitAccessTokenProviderTests.java similarity index 85% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/implicit/TestImplicitAccessTokenProvider.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/implicit/ImplicitAccessTokenProviderTests.java index 39cee7770..b6d3c7dfd 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/implicit/TestImplicitAccessTokenProvider.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/implicit/ImplicitAccessTokenProviderTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -30,7 +30,7 @@ * @author Dave Syer * */ -public class TestImplicitAccessTokenProvider { +public class ImplicitAccessTokenProviderTests { @Rule public ExpectedException expected = ExpectedException.none(); @@ -39,8 +39,8 @@ public class TestImplicitAccessTokenProvider { private ImplicitAccessTokenProvider provider = new ImplicitAccessTokenProvider() { @Override - protected OAuth2AccessToken retrieveToken(MultiValueMap form, HttpHeaders headers, - OAuth2ProtectedResourceDetails resource) { + protected OAuth2AccessToken retrieveToken(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, + MultiValueMap form, HttpHeaders headers) { params.putAll(form); return new DefaultOAuth2AccessToken("FOO"); } @@ -59,11 +59,11 @@ public void testGetAccessTokenRequest() throws Exception { AccessTokenRequest request = new DefaultAccessTokenRequest(); resource.setClientId("foo"); resource.setAccessTokenUri("http://localhost/oauth/authorize"); - resource.setPreEstablishedRedirectUri("http://anywhere.com/"); + resource.setPreEstablishedRedirectUri("https://anywhere.com/"); assertEquals("FOO", provider.obtainAccessToken(resource, request).getValue()); assertEquals("foo", params.getFirst("client_id")); assertEquals("token", params.getFirst("response_type")); - assertEquals("http://anywhere.com/", params.getFirst("redirect_uri")); + assertEquals("https://anywhere.com/", params.getFirst("redirect_uri")); } } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/password/ResourceOwnerPasswordAccessTokenProviderTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/password/ResourceOwnerPasswordAccessTokenProviderTests.java new file mode 100644 index 000000000..72df46099 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/client/token/grant/password/ResourceOwnerPasswordAccessTokenProviderTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2006-2011 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.client.token.grant.password; + +import static org.junit.Assert.assertEquals; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.http.HttpHeaders; +import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; +import org.springframework.security.oauth2.client.token.AccessTokenRequest; +import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * @author Dave Syer + * + */ +public class ResourceOwnerPasswordAccessTokenProviderTests { + + @Rule + public ExpectedException expected = ExpectedException.none(); + + private MultiValueMap params = new LinkedMultiValueMap(); + + private ResourceOwnerPasswordAccessTokenProvider provider = new ResourceOwnerPasswordAccessTokenProvider() { + @Override + protected OAuth2AccessToken retrieveToken(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, + MultiValueMap form, HttpHeaders headers) { + params.putAll(form); + if (!form.containsKey("username") || form.getFirst("username")==null) { + throw new IllegalArgumentException(); + } + // Only the map parts of the AccessTokenRequest are sent as form values + if (form.containsKey("current_uri") || form.containsKey("currentUri")) { + throw new IllegalArgumentException(); + } + return new DefaultOAuth2AccessToken("FOO"); + } + }; + + private ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails(); + + @Test + public void testGetAccessToken() throws Exception { + AccessTokenRequest request = new DefaultAccessTokenRequest(); + resource.setAccessTokenUri("http://localhost/oauth/token"); + resource.setUsername("foo"); + resource.setPassword("bar"); + assertEquals("FOO", provider.obtainAccessToken(resource, request).getValue()); + } + + @Test + public void testGetAccessTokenWithDynamicCredentials() throws Exception { + AccessTokenRequest request = new DefaultAccessTokenRequest(); + request.set("username", "foo"); + request.set("password", "bar"); + resource.setAccessTokenUri("http://localhost/oauth/token"); + assertEquals("FOO", provider.obtainAccessToken(resource, request).getValue()); + } + + @Test + public void testCurrentUriNotUsed() throws Exception { + AccessTokenRequest request = new DefaultAccessTokenRequest(); + request.set("username", "foo"); + request.setCurrentUri("urn:foo:bar"); + resource.setAccessTokenUri("http://localhost/oauth/token"); + assertEquals("FOO", provider.obtainAccessToken(resource, request).getValue()); + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/BaseOAuth2AccessTokenJacksonTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/BaseOAuth2AccessTokenJacksonTest.java index 638a96c21..175d1150d 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/BaseOAuth2AccessTokenJacksonTest.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/BaseOAuth2AccessTokenJacksonTest.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -23,7 +23,6 @@ import java.util.Set; import java.util.TreeSet; -import org.codehaus.jackson.map.ObjectMapper; import org.junit.Before; import org.junit.Rule; import org.junit.rules.ExpectedException; @@ -42,8 +41,12 @@ abstract class BaseOAuth2AccessTokenJacksonTest { protected static final String ACCESS_TOKEN_EMPTYSCOPE = "{\"access_token\":\"token-value\",\"token_type\":\"bearer\",\"refresh_token\":\"refresh-value\",\"expires_in\":10,\"scope\":\"\"}"; + protected static final String ACCESS_TOKEN_BROKENEXPIRES = "{\"access_token\":\"token-value\",\"token_type\":\"bearer\",\"refresh_token\":\"refresh-value\",\"expires_in\":\"10\",\"scope\":\"\"}"; + protected static final String ACCESS_TOKEN_MULTISCOPE = "{\"access_token\":\"token-value\",\"token_type\":\"bearer\",\"refresh_token\":\"refresh-value\",\"expires_in\":10,\"scope\":\"read write\"}"; + protected static final String ACCESS_TOKEN_ARRAYSCOPE = "{\"access_token\":\"token-value\",\"token_type\":\"bearer\",\"refresh_token\":\"refresh-value\",\"expires_in\":10,\"scope\":[\"read\",\"write\"]}"; + protected static final String ACCESS_TOKEN_NOSCOPE = "{\"access_token\":\"token-value\",\"token_type\":\"bearer\",\"refresh_token\":\"refresh-value\",\"expires_in\":10}"; protected static final String ACCESS_TOKEN_NOREFRESH = "{\"access_token\":\"token-value\",\"token_type\":\"bearer\",\"expires_in\":10}"; @@ -52,6 +55,8 @@ abstract class BaseOAuth2AccessTokenJacksonTest { protected static final String ACCESS_TOKEN_ADDITIONAL_INFO = "{\"access_token\":\"token-value\",\"token_type\":\"bearer\",\"one\":\"two\",\"three\":4,\"five\":{\"six\":7}}"; + protected static final String ACCESS_TOKEN_ZERO_EXPIRES = "{\"access_token\":\"token-value\",\"token_type\":\"bearer\",\"expires_in\":0}"; + @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestDefaultOAuth2SerializationService.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/DefaultOAuth2SerializationServiceTests.java similarity index 95% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestDefaultOAuth2SerializationService.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/DefaultOAuth2SerializationServiceTests.java index 26c86e9bf..9ab676c68 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestDefaultOAuth2SerializationService.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/DefaultOAuth2SerializationServiceTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -30,7 +30,7 @@ * @author Dave Syer * */ -public class TestDefaultOAuth2SerializationService { +public class DefaultOAuth2SerializationServiceTests { @Test public void testDefaultDeserialization() throws Exception { diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestJsonSerialization.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/JsonSerializationTests.java similarity index 96% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestJsonSerialization.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/JsonSerializationTests.java index 9d8a40368..c51fcc367 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestJsonSerialization.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/JsonSerializationTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -16,21 +16,22 @@ package org.springframework.security.oauth2.common; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import java.util.Date; -import org.codehaus.jackson.map.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; + import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + /** * @author Dave Syer * */ -public class TestJsonSerialization { +public class JsonSerializationTests { @Test public void testDefaultSerialization() throws Exception { diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson2Deserializer.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2DeserializerTests.java similarity index 81% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson2Deserializer.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2DeserializerTests.java index e7939dd50..b91503d47 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson2Deserializer.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2DeserializerTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -25,6 +25,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertFalse; /** * Tests deserialization of an {@link org.springframework.security.oauth2.common.OAuth2AccessToken} using jackson. @@ -32,7 +33,7 @@ * @author Rob Winch */ @PrepareForTest(OAuth2AccessTokenJackson2Deserializer.class) -public class TestOAuth2AccessTokenJackson2Deserializer extends BaseOAuth2AccessTokenJacksonTest { +public class OAuth2AccessTokenJackson2DeserializerTests extends BaseOAuth2AccessTokenJacksonTest { protected ObjectMapper mapper; @@ -70,12 +71,25 @@ public void readValueWithEmptyStringScope() throws JsonGenerationException, Json assertTokenEquals(accessToken, actual); } + @Test + public void readValueWithBrokenExpiresIn() throws JsonGenerationException, JsonMappingException, IOException { + accessToken.setScope(new HashSet()); + OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_BROKENEXPIRES, OAuth2AccessToken.class); + assertTokenEquals(accessToken, actual); + } + @Test public void readValueWithMultiScopes() throws Exception { OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_MULTISCOPE, OAuth2AccessToken.class); assertTokenEquals(accessToken,actual); } + @Test + public void readValueWithArrayScopes() throws Exception { + OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_ARRAYSCOPE, OAuth2AccessToken.class); + assertTokenEquals(accessToken, actual); + } + @Test public void readValueWithMac() throws Exception { accessToken.setTokenType("mac"); @@ -94,6 +108,12 @@ public void readValueWithAdditionalInformation() throws Exception { assertTokenEquals(accessToken,actual); } + @Test + public void readValueWithZeroExpiresAsNotExpired() throws Exception { + OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_ZERO_EXPIRES, OAuth2AccessToken.class); + assertFalse("Token with expires_in:0 must be treated as not expired.", actual.isExpired()); + } + private static void assertTokenEquals(OAuth2AccessToken expected, OAuth2AccessToken actual) { assertEquals(expected.getTokenType(), actual.getTokenType()); assertEquals(expected.getValue(), actual.getValue()); diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson2Serializer.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2SerializerTests.java similarity index 97% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson2Serializer.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2SerializerTests.java index fed19b806..92836dc34 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson2Serializer.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2SerializerTests.java @@ -17,7 +17,7 @@ * @author Rob Winch */ @PrepareForTest(OAuth2AccessTokenJackson2Serializer.class) -public class TestOAuth2AccessTokenJackson2Serializer extends BaseOAuth2AccessTokenJacksonTest { +public class OAuth2AccessTokenJackson2SerializerTests extends BaseOAuth2AccessTokenJacksonTest { protected ObjectMapper mapper; diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson1Deserializer.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson1Deserializer.java deleted file mode 100644 index 6631557cb..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson1Deserializer.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package org.springframework.security.oauth2.common; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import java.io.IOException; -import java.util.Date; -import java.util.HashSet; - -import org.codehaus.jackson.JsonGenerationException; -import org.codehaus.jackson.map.JsonMappingException; -import org.codehaus.jackson.map.ObjectMapper; -import org.junit.Before; -import org.junit.Test; -import org.powermock.core.classloader.annotations.PrepareForTest; - -/** - * Tests deserialization of an {@link OAuth2AccessToken} using jackson. - * - * @author Rob Winch - */ -@PrepareForTest(OAuth2AccessTokenJackson1Deserializer.class) -public class TestOAuth2AccessTokenJackson1Deserializer extends BaseOAuth2AccessTokenJacksonTest { - - protected ObjectMapper mapper; - - @Before - public void createObjectMapper() { - mapper = new ObjectMapper(); - } - - @Test - public void readValueNoRefresh() throws JsonGenerationException, JsonMappingException, IOException { - accessToken.setRefreshToken(null); - accessToken.setScope(null); - OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_NOREFRESH, OAuth2AccessToken.class); - assertTokenEquals(accessToken,actual); - } - - @Test - public void readValueWithRefresh() throws JsonGenerationException, JsonMappingException, IOException { - accessToken.setScope(null); - OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_NOSCOPE, OAuth2AccessToken.class); - assertTokenEquals(accessToken,actual); - } - - @Test - public void readValueWithSingleScopes() throws JsonGenerationException, JsonMappingException, IOException { - accessToken.getScope().remove(accessToken.getScope().iterator().next()); - OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_SINGLESCOPE, OAuth2AccessToken.class); - assertTokenEquals(accessToken,actual); - } - - @Test - public void readValueWithEmptyStringScope() throws JsonGenerationException, JsonMappingException, IOException { - accessToken.setScope(new HashSet()); - OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_EMPTYSCOPE, OAuth2AccessToken.class); - assertTokenEquals(accessToken, actual); - } - - @Test - public void readValueWithMultiScopes() throws Exception { - OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_MULTISCOPE, OAuth2AccessToken.class); - assertTokenEquals(accessToken,actual); - } - - @Test - public void readValueWithMac() throws Exception { - accessToken.setTokenType("mac"); - String encodedToken = ACCESS_TOKEN_MULTISCOPE.replace("bearer", accessToken.getTokenType()); - OAuth2AccessToken actual = mapper.readValue(encodedToken, OAuth2AccessToken.class); - assertTokenEquals(accessToken,actual); - } - - @Test - public void readValueWithAdditionalInformation() throws Exception { - OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_ADDITIONAL_INFO, OAuth2AccessToken.class); - accessToken.setAdditionalInformation(additionalInformation); - accessToken.setRefreshToken(null); - accessToken.setScope(null); - accessToken.setExpiration(null); - assertTokenEquals(accessToken,actual); - } - - private static void assertTokenEquals(OAuth2AccessToken expected, OAuth2AccessToken actual) { - assertEquals(expected.getTokenType(), actual.getTokenType()); - assertEquals(expected.getValue(), actual.getValue()); - - OAuth2RefreshToken expectedRefreshToken = expected.getRefreshToken(); - if (expectedRefreshToken == null) { - assertNull(actual.getRefreshToken()); - } - else { - assertEquals(expectedRefreshToken.getValue(), actual.getRefreshToken().getValue()); - } - assertEquals(expected.getScope(), actual.getScope()); - Date expectedExpiration = expected.getExpiration(); - if (expectedExpiration == null) { - assertNull(actual.getExpiration()); - } - else { - assertEquals(expectedExpiration.getTime(), actual.getExpiration().getTime()); - } - assertEquals(expected.getAdditionalInformation(), actual.getAdditionalInformation()); - } -} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson1Serializer.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson1Serializer.java deleted file mode 100644 index b0923f118..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson1Serializer.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.springframework.security.oauth2.common; - -import static org.junit.Assert.assertEquals; - -import java.io.IOException; - -import org.codehaus.jackson.JsonGenerationException; -import org.codehaus.jackson.map.JsonMappingException; -import org.codehaus.jackson.map.ObjectMapper; -import org.junit.Before; -import org.junit.Test; -import org.powermock.core.classloader.annotations.PrepareForTest; - -/** - * Tests serialization of an {@link OAuth2AccessToken} using jackson. - * - * @author Rob Winch - */ -@PrepareForTest(OAuth2AccessTokenJackson1Serializer.class) -public class TestOAuth2AccessTokenJackson1Serializer extends BaseOAuth2AccessTokenJacksonTest { - - protected ObjectMapper mapper; - - @Before - public void createObjectMapper() { - mapper = new ObjectMapper(); - } - - @Test - public void writeValueAsStringNoRefresh() throws JsonGenerationException, JsonMappingException, IOException { - accessToken.setRefreshToken(null); - accessToken.setScope(null); - String encodedAccessToken = mapper.writeValueAsString(accessToken); - assertEquals(BaseOAuth2AccessTokenJacksonTest.ACCESS_TOKEN_NOREFRESH, encodedAccessToken); - } - - @Test - public void writeValueAsStringWithRefresh() throws JsonGenerationException, JsonMappingException, IOException { - accessToken.setScope(null); - String encodedAccessToken = mapper.writeValueAsString(accessToken); - assertEquals(BaseOAuth2AccessTokenJacksonTest.ACCESS_TOKEN_NOSCOPE, encodedAccessToken); - } - - @Test - public void writeValueAsStringWithEmptyScope() throws JsonGenerationException, JsonMappingException, IOException { - accessToken.getScope().clear(); - String encodedAccessToken = mapper.writeValueAsString(accessToken); - assertEquals(BaseOAuth2AccessTokenJacksonTest.ACCESS_TOKEN_NOSCOPE, encodedAccessToken); - } - - @Test - public void writeValueAsStringWithSingleScopes() throws JsonGenerationException, JsonMappingException, IOException { - accessToken.getScope().remove(accessToken.getScope().iterator().next()); - String encodedAccessToken = mapper.writeValueAsString(accessToken); - assertEquals(BaseOAuth2AccessTokenJacksonTest.ACCESS_TOKEN_SINGLESCOPE, encodedAccessToken); - } - - @Test - public void writeValueAsStringWithNullScope() throws JsonGenerationException, JsonMappingException, IOException { - thrown.expect(JsonMappingException.class); - thrown.expectMessage("Scopes cannot be null or empty. Got [null]"); - - accessToken.getScope().clear(); - try { - accessToken.getScope().add(null); - } - catch (NullPointerException e) { - // short circuit NPE from Java 7 (which is correct but only relevant for this test) - throw new JsonMappingException("Scopes cannot be null or empty. Got [null]"); - } - mapper.writeValueAsString(accessToken); - } - - @Test - public void writeValueAsStringWithEmptyStringScope() throws JsonGenerationException, JsonMappingException, - IOException { - thrown.expect(JsonMappingException.class); - thrown.expectMessage("Scopes cannot be null or empty. Got []"); - - accessToken.getScope().clear(); - accessToken.getScope().add(""); - mapper.writeValueAsString(accessToken); - } - - @Test - public void writeValueAsStringWithQuoteInScope() throws JsonGenerationException, JsonMappingException, IOException { - accessToken.getScope().add("\""); - String encodedAccessToken = mapper.writeValueAsString(accessToken); - assertEquals( - "{\"access_token\":\"token-value\",\"token_type\":\"bearer\",\"refresh_token\":\"refresh-value\",\"expires_in\":10,\"scope\":\"\\\" read write\"}", - encodedAccessToken); - } - - @Test - public void writeValueAsStringWithMultiScopes() throws JsonGenerationException, JsonMappingException, IOException { - String encodedAccessToken = mapper.writeValueAsString(accessToken); - assertEquals(ACCESS_TOKEN_MULTISCOPE, encodedAccessToken); - } - - @Test - public void writeValueAsStringWithMac() throws Exception { - accessToken.setTokenType("mac"); - String expectedEncodedAccessToken = ACCESS_TOKEN_MULTISCOPE.replace("bearer", accessToken.getTokenType()); - String encodedAccessToken = mapper.writeValueAsString(accessToken); - assertEquals(expectedEncodedAccessToken, encodedAccessToken); - } - - @Test - public void writeValueWithAdditionalInformation() throws JsonGenerationException, JsonMappingException, IOException { - accessToken.setRefreshToken(null); - accessToken.setScope(null); - accessToken.setExpiration(null); - accessToken.setAdditionalInformation(additionalInformation); - String encodedAccessToken = mapper.writeValueAsString(accessToken); - assertEquals(BaseOAuth2AccessTokenJacksonTest.ACCESS_TOKEN_ADDITIONAL_INFO, encodedAccessToken); - } - -} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/TestOAuth2ExceptionDeserializer.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/OAuth2ExceptionDeserializerTests.java similarity index 85% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/TestOAuth2ExceptionDeserializer.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/OAuth2ExceptionDeserializerTests.java index e09a90568..5fe77127e 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/TestOAuth2ExceptionDeserializer.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/OAuth2ExceptionDeserializerTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -12,11 +12,11 @@ */ package org.springframework.security.oauth2.common.exception; -import static org.junit.Assert.assertEquals; - -import org.codehaus.jackson.map.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.BeforeClass; import org.junit.Test; + +import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException; import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; @@ -28,12 +28,15 @@ import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException; import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException; +import static org.junit.Assert.assertEquals; + /** * * @author Rob Winch + * @author Dave Syer * */ -public class TestOAuth2ExceptionDeserializer { +public class OAuth2ExceptionDeserializerTests { private static final String DETAILS = "some detail"; private static ObjectMapper mapper; @@ -66,6 +69,14 @@ public void readValueInvalidScope() throws Exception { assertEquals(null,result.getAdditionalInformation()); } + @Test + public void readValueIsufficientScope() throws Exception { + String accessToken = "{\"error\": \"insufficient_scope\", \"error_description\": \"insufficient scope\", \"scope\": \"bar foo\"}"; + InsufficientScopeException result = (InsufficientScopeException) mapper.readValue(accessToken, OAuth2Exception.class); + assertEquals("insufficient scope",result.getMessage()); + assertEquals("bar foo",result.getAdditionalInformation().get("scope").toString()); + } + @Test public void readValueUnsupportedGrantType() throws Exception { String accessToken = createResponse(OAuth2Exception.UNSUPPORTED_GRANT_TYPE); @@ -95,10 +106,10 @@ public void readValueAccessDenied() throws Exception { @Test public void readValueRedirectUriMismatch() throws Exception { - String accessToken = createResponse(OAuth2Exception.REDIRECT_URI_MISMATCH); + String accessToken = createResponse(OAuth2Exception.INVALID_GRANT, "Redirect URI mismatch."); RedirectMismatchException result = (RedirectMismatchException) mapper.readValue(accessToken, OAuth2Exception.class); - assertEquals(DETAILS,result.getMessage()); + assertEquals("Redirect URI mismatch.",result.getMessage()); assertEquals(null,result.getAdditionalInformation()); } @@ -142,8 +153,12 @@ public void readValueWithObjects() throws Exception { assertEquals("{foo=[bar]}",result.getAdditionalInformation().toString()); } + private String createResponse(String error, String message) { + return "{\"error\":\"" + error + "\",\"error_description\":\""+message+"\"}"; + } + private String createResponse(String error) { - return "{\"error\":\"" + error + "\",\"error_description\":\"some detail\"}"; + return createResponse(error, DETAILS); } } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/OAuth2ExceptionJackson2DeserializerTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/OAuth2ExceptionJackson2DeserializerTests.java new file mode 100644 index 000000000..3e14b01fa --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/OAuth2ExceptionJackson2DeserializerTests.java @@ -0,0 +1,163 @@ +/* + * Copyright 2011 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.common.exception; + +import static org.junit.Assert.assertEquals; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.security.oauth2.common.exceptions.*; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * + * @author Rob Winch + * @author Dave Syer + * + */ +public class OAuth2ExceptionJackson2DeserializerTests { + private static final String DETAILS = "some detail"; + private static ObjectMapper mapper; + + @BeforeClass + public static void setUpClass() { + mapper = new ObjectMapper(); + } + + @Test + public void readValueInvalidGrant() throws Exception { + String accessToken = createResponse(OAuth2Exception.INVALID_GRANT); + InvalidGrantException result = (InvalidGrantException) mapper.readValue(accessToken, OAuth2Exception.class); + assertEquals(DETAILS,result.getMessage()); + assertEquals(null,result.getAdditionalInformation()); + } + + @Test + public void readValueInvalidRequest() throws Exception { + String accessToken = createResponse(OAuth2Exception.INVALID_REQUEST); + InvalidRequestException result = (InvalidRequestException) mapper.readValue(accessToken, OAuth2Exception.class); + assertEquals(DETAILS,result.getMessage()); + assertEquals(null,result.getAdditionalInformation()); + } + + @Test + public void readValueInvalidScope() throws Exception { + String accessToken = createResponse(OAuth2Exception.INVALID_SCOPE); + InvalidScopeException result = (InvalidScopeException) mapper.readValue(accessToken, OAuth2Exception.class); + assertEquals(DETAILS,result.getMessage()); + assertEquals(null,result.getAdditionalInformation()); + } + + @Test + public void readValueIsufficientScope() throws Exception { + String accessToken = "{\"error\": \"insufficient_scope\", \"error_description\": \"insufficient scope\", \"scope\": \"bar foo\"}"; + InsufficientScopeException result = (InsufficientScopeException) mapper.readValue(accessToken, OAuth2Exception.class); + assertEquals("insufficient scope",result.getMessage()); + assertEquals("bar foo",result.getAdditionalInformation().get("scope").toString()); + } + + @Test + public void readValueUnsupportedGrantType() throws Exception { + String accessToken = createResponse(OAuth2Exception.UNSUPPORTED_GRANT_TYPE); + UnsupportedGrantTypeException result = (UnsupportedGrantTypeException) mapper.readValue(accessToken, + OAuth2Exception.class); + assertEquals(DETAILS,result.getMessage()); + assertEquals(null,result.getAdditionalInformation()); + } + + @Test + public void readValueUnauthorizedClient() throws Exception { + String accessToken = createResponse(OAuth2Exception.UNAUTHORIZED_CLIENT); + UnauthorizedClientException result = (UnauthorizedClientException) mapper.readValue(accessToken, + OAuth2Exception.class); + assertEquals(DETAILS,result.getMessage()); + assertEquals(null,result.getAdditionalInformation()); + } + + @Test + public void readValueAccessDenied() throws Exception { + String accessToken = createResponse(OAuth2Exception.ACCESS_DENIED); + UserDeniedAuthorizationException result = (UserDeniedAuthorizationException) mapper.readValue(accessToken, + OAuth2Exception.class); + assertEquals(DETAILS,result.getMessage()); + assertEquals(null,result.getAdditionalInformation()); + } + + @Test + public void readValueRedirectUriMismatch() throws Exception { + String accessToken = createResponse(OAuth2Exception.INVALID_GRANT, "Redirect URI mismatch."); + RedirectMismatchException result = (RedirectMismatchException) mapper.readValue(accessToken, + OAuth2Exception.class); + assertEquals("Redirect URI mismatch.",result.getMessage()); + assertEquals(null,result.getAdditionalInformation()); + } + + @Test + public void readValueInvalidToken() throws Exception { + String accessToken = createResponse(OAuth2Exception.INVALID_TOKEN); + InvalidTokenException result = (InvalidTokenException) mapper.readValue(accessToken, OAuth2Exception.class); + assertEquals(DETAILS,result.getMessage()); + assertEquals(null,result.getAdditionalInformation()); + } + + @Test + public void readValueUndefinedException() throws Exception { + String accessToken = createResponse("notdefinedcode"); + OAuth2Exception result = mapper.readValue(accessToken, OAuth2Exception.class); + assertEquals(DETAILS,result.getMessage()); + assertEquals(null,result.getAdditionalInformation()); + } + + @Test + public void readValueInvalidClient() throws Exception { + String accessToken = createResponse(OAuth2Exception.INVALID_CLIENT); + InvalidClientException result = (InvalidClientException) mapper.readValue(accessToken, OAuth2Exception.class); + assertEquals(DETAILS,result.getMessage()); + assertEquals(null,result.getAdditionalInformation()); + } + + @Test + public void readValueWithAdditionalDetails() throws Exception { + String accessToken = "{\"error\": \"invalid_client\", \"error_description\": \"some detail\", \"foo\": \"bar\"}"; + InvalidClientException result = (InvalidClientException) mapper.readValue(accessToken, OAuth2Exception.class); + assertEquals(DETAILS,result.getMessage()); + assertEquals("{foo=bar}",result.getAdditionalInformation().toString()); + } + + @Test + public void readValueWithObjects() throws Exception { + String accessToken = "{\"error\": [\"invalid\",\"client\"], \"error_description\": {\"some\":\"detail\"}, \"foo\": [\"bar\"]}"; + OAuth2Exception result = mapper.readValue(accessToken, OAuth2Exception.class); + assertEquals("{some=detail}",result.getMessage()); + assertEquals("{foo=[bar]}",result.getAdditionalInformation().toString()); + } + + // gh-594 + @Test + public void readValueWithNullErrorDescription() throws Exception { + OAuth2Exception ex = new OAuth2Exception(null); + OAuth2Exception result = mapper.readValue(mapper.writeValueAsString(ex), OAuth2Exception.class); + // Null error description defaults to error code when deserialized + assertEquals(ex.getOAuth2ErrorCode(), result.getMessage()); + } + + private String createResponse(String error, String message) { + return "{\"error\":\"" + error + "\",\"error_description\":\""+message+"\"}"; + } + + private String createResponse(String error) { + return createResponse(error, DETAILS); + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/TestOAuth2ExceptionSerializer.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/OAuth2ExceptionSerializerTests.java similarity index 97% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/TestOAuth2ExceptionSerializer.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/OAuth2ExceptionSerializerTests.java index 45e875a99..59ed4f755 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/TestOAuth2ExceptionSerializer.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/exception/OAuth2ExceptionSerializerTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -12,12 +12,11 @@ */ package org.springframework.security.oauth2.common.exception; -import static org.junit.Assert.assertEquals; - -import org.codehaus.jackson.map.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.After; import org.junit.BeforeClass; import org.junit.Test; + import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; @@ -29,12 +28,14 @@ import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException; import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException; +import static org.junit.Assert.assertEquals; + /** * * @author Rob Winch * */ -public class TestOAuth2ExceptionSerializer { +public class OAuth2ExceptionSerializerTests { private static final String DETAILS = "some detail"; private static ObjectMapper mapper; diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/util/CustomSerializationStrategyTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/util/CustomSerializationStrategyTests.java new file mode 100644 index 000000000..cbe70acc6 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/util/CustomSerializationStrategyTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2012-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.common.util; + + +import org.company.oauth2.CustomAuthentication; +import org.company.oauth2.CustomOAuth2AccessToken; +import org.company.oauth2.CustomOAuth2Authentication; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.RequestTokenFactory; + +import java.io.Serializable; +import java.util.*; + +import static org.junit.Assert.*; +import static org.powermock.api.mockito.PowerMockito.spy; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ SpringFactoriesLoader.class }) +public class CustomSerializationStrategyTests { + + @Test + public void loadCustomSerializationStrategy() { + spy(SpringFactoriesLoader.class); + when(SpringFactoriesLoader + .loadFactories(SerializationStrategy.class, SerializationUtils.class.getClassLoader())) + .thenReturn(Arrays.asList(new CustomSerializationStrategy())); + + deserialize(new DefaultOAuth2AccessToken("access-token-" + UUID.randomUUID())); + + deserialize(new OAuth2Authentication( + new OAuth2Request(Collections.emptyMap(), "clientId", Collections.emptyList(), + false, Collections.emptySet(), + new HashSet(Arrays.asList("resourceId-1", "resourceId-2")), "redirectUri", + Collections.emptySet(), Collections.emptyMap()), + new UsernamePasswordAuthenticationToken("test", "N/A"))); + + deserialize(new DefaultExpiringOAuth2RefreshToken( + "access-token-" + UUID.randomUUID(), new Date())); + + deserialize("xyz"); + deserialize(new HashMap()); + + deserialize(new CustomOAuth2AccessToken("xyz")); + + deserialize( + new CustomOAuth2Authentication( + RequestTokenFactory.createOAuth2Request("id", false), + new CustomAuthentication("test", false))); + } + + private void deserialize(Object object) { + byte[] bytes = SerializationUtils.serialize(object); + assertNotNull(bytes); + assertTrue(bytes.length > 0); + + Object clone = SerializationUtils.deserialize(bytes); + assertNotNull(clone); + assertEquals(object, clone); + } + + private static class CustomSerializationStrategy extends WhitelistedSerializationStrategy { + + private static final List ALLOWED_CLASSES = new ArrayList(); + static { + ALLOWED_CLASSES.add("java.lang."); + ALLOWED_CLASSES.add("java.util."); + ALLOWED_CLASSES.add("org.springframework.security."); + ALLOWED_CLASSES.add("org.company.oauth2."); + } + + CustomSerializationStrategy() { + super(ALLOWED_CLASSES); + } + + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/util/RandomValueStringGeneratorTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/util/RandomValueStringGeneratorTests.java new file mode 100644 index 000000000..7f4d8bab5 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/util/RandomValueStringGeneratorTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-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.common.util; + +import org.junit.Before; +import org.junit.Test; + +import java.security.SecureRandom; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Tests for {@link RandomValueStringGenerator} + * + * @author Josh Kerr + */ +public class RandomValueStringGeneratorTests { + + private RandomValueStringGenerator generator; + + @Before + public void setup() { + generator = new RandomValueStringGenerator(); + } + + @Test + public void generate() { + String value = generator.generate(); + assertNotNull(value); + assertEquals("Authorization code is not correct size", 6, value.length()); + } + + @Test + public void generate_LargeLengthOnConstructor() { + generator = new RandomValueStringGenerator(1024); + String value = generator.generate(); + assertNotNull(value); + assertEquals("Authorization code is not correct size", 1024, value.length()); + } + + @Test + public void getAuthorizationCodeString() { + byte[] bytes = new byte[10]; + new SecureRandom().nextBytes(bytes); + String value = generator.getAuthorizationCodeString(bytes); + assertNotNull(value); + assertEquals("Authorization code is not correct size", 10, value.length()); + } + + @Test + public void setLength() { + generator.setLength(12); + String value = generator.generate(); + assertEquals("Authorization code is not correct size", 12, value.length()); + } + + @Test(expected = IllegalArgumentException.class) + public void setLength_NonPositiveNumber() { + generator.setLength(-1); + generator.generate(); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/util/SerializationUtilsTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/util/SerializationUtilsTests.java new file mode 100644 index 000000000..fd73714f4 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/common/util/SerializationUtilsTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-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.common.util; + +import org.company.oauth2.CustomOAuth2AccessToken; +import org.junit.Test; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.UUID; + +import static org.junit.Assert.*; + +/** + * @author Artem Smotrakov + */ +public class SerializationUtilsTests { + + @Test + public void deserializeAllowedClasses() { + deserializeAllowedClasses(new DefaultOAuth2AccessToken("access-token-" + UUID.randomUUID())); + + deserializeAllowedClasses(new OAuth2Authentication( + new OAuth2Request(Collections.emptyMap(), "clientId", Collections.emptyList(), + false, Collections.emptySet(), + new HashSet(Arrays.asList("resourceId-1", "resourceId-2")), "redirectUri", + Collections.emptySet(), Collections.emptyMap()), + new UsernamePasswordAuthenticationToken("test", "N/A"))); + + deserializeAllowedClasses(new DefaultExpiringOAuth2RefreshToken( + "access-token-" + UUID.randomUUID(), new Date())); + + deserializeAllowedClasses("xyz"); + deserializeAllowedClasses(new HashMap()); + } + + private void deserializeAllowedClasses(Object object) { + byte[] bytes = SerializationUtils.serialize(object); + assertNotNull(bytes); + assertTrue(bytes.length > 0); + + Object clone = SerializationUtils.deserialize(bytes); + assertNotNull(clone); + assertEquals(object, clone); + } + + @Test + public void deserializeCustomClasses() { + OAuth2AccessToken accessToken = new CustomOAuth2AccessToken("FOO"); + byte[] bytes = SerializationUtils.serialize(accessToken); + OAuth2AccessToken clone = SerializationUtils.deserialize(bytes); + assertNotNull(clone); + assertEquals(accessToken, clone); + } + + @Test(expected = IllegalArgumentException.class) + public void deserializeNotAllowedCustomClasses() { + OAuth2AccessToken accessToken = new CustomOAuth2AccessToken("FOO"); + WhitelistedSerializationStrategy newStrategy = new WhitelistedSerializationStrategy(); + SerializationStrategy oldStrategy = SerializationUtils.getSerializationStrategy(); + try { + SerializationUtils.setSerializationStrategy(newStrategy); + byte[] bytes = SerializationUtils.serialize(accessToken); + OAuth2AccessToken clone = SerializationUtils.deserialize(bytes); + assertNotNull(clone); + assertEquals(accessToken, clone); + } finally { + SerializationUtils.setSerializationStrategy(oldStrategy); + } + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/AuthorizationServerConfigurationTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/AuthorizationServerConfigurationTests.java new file mode 100644 index 000000000..a5544dce6 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/AuthorizationServerConfigurationTests.java @@ -0,0 +1,879 @@ +/* + * Copyright 2006-2011 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.config.annotation; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.mockito.Mockito; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.mock.web.MockServletContext; +import org.springframework.security.authentication.AnonymousAuthenticationProvider; +import org.springframework.security.authentication.AuthenticationEventPublisher; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.TestingAuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; +import org.springframework.security.config.authentication.AuthenticationManagerBeanDefinitionParser; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.jwt.crypto.sign.MacSigner; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler; +import org.springframework.security.oauth2.provider.approval.TokenApprovalStore; +import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; +import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter; +import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService; +import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint; +import org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint; +import org.springframework.security.oauth2.provider.endpoint.RedirectResolver; +import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint; +import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; +import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import javax.servlet.Filter; +import javax.sql.DataSource; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.*; + +/** + * @author Dave Syer + * + */ +@RunWith(Parameterized.class) +public class AuthorizationServerConfigurationTests { + + private AnnotationConfigWebApplicationContext context; + + @Rule + public ExpectedException expected = ExpectedException.none(); + + private Class[] resources; + + @Parameters + public static List parameters() { + return Arrays.asList( // @formatter:off + new Object[] { BeanCreationException.class, new Class[] { AuthorizationServerUnconfigured.class } }, + new Object[] { null, new Class[] { AuthorizationServerCycle.class } }, + new Object[] { null, new Class[] { AuthorizationServerVanilla.class } }, + new Object[] { null, new Class[] { AuthorizationServerDisableApproval.class } }, + new Object[] { null, new Class[] { AuthorizationServerExtras.class } }, + new Object[] { null, new Class[] { AuthorizationServerJdbc.class } }, + new Object[] { null, new Class[] { AuthorizationServerEncoder.class } }, + new Object[] { null, new Class[] { AuthorizationServerJwt.class } }, + new Object[] { null, new Class[] { AuthorizationServerJwtCustomSigner.class } }, + new Object[] { null, new Class[] { AuthorizationServerWithTokenServices.class } }, + new Object[] { null, new Class[] { AuthorizationServerApproval.class } }, + new Object[] { null, new Class[] { AuthorizationServerExceptionTranslator.class } }, + new Object[] { null, new Class[] { AuthorizationServerCustomClientDetails.class } }, + new Object[] { null, new Class[] { AuthorizationServerAllowsSpecificRequestMethods.class } }, + new Object[] { null, new Class[] { AuthorizationServerAllowsOnlyPost.class } }, + new Object[] { BeanCreationException.class, new Class[] { AuthorizationServerTypes.class } }, + new Object[] { null, new Class[] { AuthorizationServerCustomGranter.class } }, + new Object[] { null, new Class[] { AuthorizationServerSslEnabled.class } }, + new Object[] { null, new Class[] { AuthorizationServerCustomRedirectResolver.class } }, + new Object[] { null, new Class[] { AuthorizationServerDefaultRedirectResolver.class } }, + new Object[] { null, new Class[] { AuthorizationServerCustomAuthenticationProvidersOnTokenEndpoint.class } }, + new Object[] { null, new Class[] { AuthorizationServerDefaultAuthenticationProviderOnTokenEndpoint.class } }, + new Object[] { null, new Class[] { AuthorizationServerCustomAuthenticationEventPublisher.class } } + // @formatter:on + ); + } + + public AuthorizationServerConfigurationTests(Class error, Class... resource) { + if (error != null) { + expected.expect(error); + } + this.resources = resource; + context = new AnnotationConfigWebApplicationContext(); + context.setServletContext(new MockServletContext()); + context.register(resource); + } + + @After + public void close() { + if (context != null) { + context.close(); + } + } + + @Test + public void testDefaults() { + context.refresh(); + assertTrue(context.containsBeanDefinition("authorizationEndpoint")); + assertNotNull(context.getBean("authorizationEndpoint", AuthorizationEndpoint.class)); + for (Class resource : resources) { + if (Runnable.class.isAssignableFrom(resource)) { + ((Runnable) context.getBean(resource)).run(); + } + } + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerUnconfigured { + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerVanilla extends AuthorizationServerConfigurerAdapter implements Runnable { + @Autowired + private AuthorizationEndpoint endpoint; + + @Autowired + private ClientDetailsService clientDetailsService; + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.inMemory().withClient("my-trusted-client") + .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit") + .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT").scopes("read", "write", "trust") + .accessTokenValiditySeconds(60).additionalInformation("foo:bar", "spam:bucket", "crap", "bad:"); + // @formatter:on + } + + @Override + public void run() { + // With no explicit approval store we still expect to see scopes in + // the user approval model + UserApprovalHandler handler = (UserApprovalHandler) ReflectionTestUtils.getField(endpoint, + "userApprovalHandler"); + AuthorizationRequest authorizationRequest = new AuthorizationRequest(); + authorizationRequest.setScope(Arrays.asList("read")); + Map request = handler.getUserApprovalRequest(authorizationRequest, + new UsernamePasswordAuthenticationToken("user", "password")); + assertTrue(request.containsKey("scopes")); + + Map information = clientDetailsService.loadClientByClientId("my-trusted-client") + .getAdditionalInformation(); + + assertTrue(information.containsKey("foo")); + assertTrue(information.get("foo").equals("bar")); + assertTrue(information.get("spam").equals("bucket")); + assertTrue(information.get("crap") == null); + assertTrue(information.get("bad").equals("")); + } + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerCycle extends AuthorizationServerConfigurerAdapter implements Runnable { + @Autowired + private AuthorizationServerTokenServices tokenServices; + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.tokenServices(tokenServices); // cycle can lead to null + // here + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.inMemory().withClient("my-trusted-client").authorizedGrantTypes("password"); + // @formatter:on + } + + @Override + public void run() { + assertNotNull(tokenServices); + } + + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerDisableApproval extends AuthorizationServerConfigurerAdapter + implements Runnable { + + @Autowired + private AuthorizationEndpoint endpoint; + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.inMemory().withClient("my-trusted-client").authorizedGrantTypes("password"); + // @formatter:on + } + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.approvalStoreDisabled(); + } + + @Override + public void run() { + // There should be no scopes in the approval model + UserApprovalHandler handler = (UserApprovalHandler) ReflectionTestUtils.getField(endpoint, + "userApprovalHandler"); + AuthorizationRequest authorizationRequest = new AuthorizationRequest(); + authorizationRequest.setScope(Arrays.asList("read")); + Map request = handler.getUserApprovalRequest(authorizationRequest, + new UsernamePasswordAuthenticationToken("user", "password")); + assertFalse(request.containsKey("scopes")); + } + + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerAllowsSpecificRequestMethods extends AuthorizationServerConfigurerAdapter + implements Runnable { + + @Autowired + private TokenEndpoint endpoint; + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.inMemory().withClient("my-trusted-client").authorizedGrantTypes("password"); + // @formatter:on + } + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.PUT); + } + + @Override + public void run() { + @SuppressWarnings("unchecked") + Set allowedRequestMethods = (Set) ReflectionTestUtils.getField(endpoint, + "allowedRequestMethods"); + assertTrue(allowedRequestMethods.contains(HttpMethod.GET)); + assertTrue(allowedRequestMethods.contains(HttpMethod.PUT)); + assertFalse(allowedRequestMethods.contains(HttpMethod.POST)); + } + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerAllowsOnlyPost extends AuthorizationServerConfigurerAdapter + implements Runnable { + + @Autowired + private TokenEndpoint endpoint; + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.inMemory().withClient("my-trusted-client").authorizedGrantTypes("password"); + // @formatter:on + } + + @Override + public void run() { + @SuppressWarnings("unchecked") + Set allowedRequestMethods = (Set) ReflectionTestUtils.getField(endpoint, + "allowedRequestMethods"); + assertFalse(allowedRequestMethods.contains(HttpMethod.GET)); + assertTrue(allowedRequestMethods.contains(HttpMethod.POST)); + } + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerExtras extends AuthorizationServerConfigurerAdapter implements Runnable { + + private TokenStore tokenStore = new InMemoryTokenStore(); + + @Autowired + private ApplicationContext context; + + @Bean + public DefaultUserApprovalHandler userApprovalHandler() { + return new DefaultUserApprovalHandler(); + } + + @Bean + public TokenApprovalStore approvalStore() { + return new TokenApprovalStore(); + } + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.tokenStore(tokenStore).approvalStore(approvalStore()).userApprovalHandler(userApprovalHandler()) + .addInterceptor(new HandlerInterceptorAdapter() { + }); + } + + @Override + public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { + oauthServer.realm("oauth/sparklr"); + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.inMemory().withClient("my-trusted-client") + .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit") + .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT").scopes("read", "write", "trust") + .accessTokenValiditySeconds(60); + // @formatter:on + } + + @Override + public void run() { + assertNotNull(context.getBean("clientDetailsService", ClientDetailsService.class) + .loadClientByClientId("my-trusted-client")); + assertNotNull( + ReflectionTestUtils.getField(context.getBean(AuthorizationEndpoint.class), "userApprovalHandler")); + } + + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerJdbc extends AuthorizationServerConfigurerAdapter { + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.tokenStore(new JdbcTokenStore(dataSource())); + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.jdbc(dataSource()).withClient("my-trusted-client").authorizedGrantTypes("password"); + // @formatter:on + } + + @Bean + public DataSource dataSource() { + return Mockito.mock(DataSource.class); + } + + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerEncoder extends AuthorizationServerConfigurerAdapter { + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.inMemory().withClient("my-trusted-client").secret(new BCryptPasswordEncoder().encode("secret")) + .authorizedGrantTypes("client_credentials"); + // @formatter:on + } + + @Override + public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { + oauthServer.passwordEncoder(new BCryptPasswordEncoder()); + } + + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerJwt extends AuthorizationServerConfigurerAdapter { + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtTokenEnhancer()); + } + + @Bean + public TokenStore tokenStore() { + return new JwtTokenStore(jwtTokenEnhancer()); + } + + @Bean + protected JwtAccessTokenConverter jwtTokenEnhancer() { + return new JwtAccessTokenConverter(); + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.inMemory().withClient("my-trusted-client").authorizedGrantTypes("password"); + // @formatter:on + } + + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerJwtCustomSigner extends AuthorizationServerConfigurerAdapter { + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtTokenEnhancer()); + } + + @Bean + public TokenStore tokenStore() { + return new JwtTokenStore(jwtTokenEnhancer()); + } + + @Bean + protected JwtAccessTokenConverter jwtTokenEnhancer() { + JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); + MacSigner verifier = new MacSigner("foobar"); + converter.setSigner(verifier); + converter.setVerifier(verifier); + return converter; + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.inMemory().withClient("my-trusted-client").authorizedGrantTypes("password"); + // @formatter:on + } + + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerWithTokenServices extends AuthorizationServerConfigurerAdapter + implements Runnable { + + @Autowired + private ClientDetailsService clientDetailsService; + + @Autowired + private ApplicationContext context; + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.tokenServices(tokenServices()).tokenStore(tokenStore()); + } + + @Bean + public DefaultTokenServices tokenServices() { + DefaultTokenServices tokenServices = new DefaultTokenServices(); + tokenServices.setTokenStore(tokenStore()); + tokenServices.setAccessTokenValiditySeconds(300); + tokenServices.setRefreshTokenValiditySeconds(30000); + tokenServices.setClientDetailsService(clientDetailsService); + return tokenServices; + } + + @Bean + public TokenStore tokenStore() { + return new InMemoryTokenStore(); + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.inMemory().withClient("my-trusted-client").authorizedGrantTypes("password"); + // @formatter:on + } + + @Override + public void run() { + assertNotNull( + ReflectionTestUtils.getField(context.getBean(CheckTokenEndpoint.class), "accessTokenConverter")); + } + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerApproval extends AuthorizationServerConfigurerAdapter + implements Runnable { + + private TokenStore tokenStore = new InMemoryTokenStore(); + + @Autowired + private ApplicationContext context; + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.tokenStore(tokenStore); + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.inMemory().withClient("my-trusted-client").authorizedGrantTypes("password"); + // @formatter:on + } + + @Override + public void run() { + assertNotNull( + ReflectionTestUtils.getField(context.getBean(AuthorizationEndpoint.class), "userApprovalHandler")); + } + + } + + @EnableWebSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerCustomRedirectResolver extends AuthorizationServerConfigurerAdapter + implements Runnable { + + @Autowired + private ApplicationContext context; + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.redirectResolver(new CustomRedirectResolver()); + } + + @Override + public void run() { + RedirectResolver resolver = (RedirectResolver) ReflectionTestUtils.getField(context.getBean(AuthorizationEndpoint.class), "redirectResolver"); + + assertNotNull(resolver); + assertTrue(resolver instanceof CustomRedirectResolver); + } + + static class CustomRedirectResolver implements RedirectResolver { + @Override + public String resolveRedirect(final String requestedRedirect, final ClientDetails client) throws OAuth2Exception { + return "go/here"; + } + } + } + + @EnableWebSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerDefaultRedirectResolver extends AuthorizationServerConfigurerAdapter + implements Runnable { + + @Autowired + private ApplicationContext context; + + @Override + public void run() { + assertNotNull( + ReflectionTestUtils.getField(context.getBean(AuthorizationEndpoint.class), "redirectResolver")); + } + + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerExceptionTranslator extends AuthorizationServerConfigurerAdapter + implements Runnable { + + private TokenStore tokenStore = new InMemoryTokenStore(); + + @Autowired + private ApplicationContext context; + + private DefaultWebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator(); + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.tokenStore(tokenStore).exceptionTranslator(exceptionTranslator); + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.inMemory().withClient("my-trusted-client").authorizedGrantTypes("password"); + // @formatter:on + } + + @Override + public void run() { + assertEquals(exceptionTranslator, ReflectionTestUtils.getField(context.getBean(AuthorizationEndpoint.class), + "providerExceptionHandler")); + } + + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerCustomGranter extends AuthorizationServerConfigurerAdapter + implements Runnable { + + @Autowired + private ApplicationContext context; + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.tokenGranter( + new ClientCredentialsTokenGranter(endpoints.getDefaultAuthorizationServerTokenServices(), + endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory())); + } + + @Override + public void run() { + assertTrue(ReflectionTestUtils.getField(context.getBean(TokenEndpoint.class), + "tokenGranter") instanceof ClientCredentialsTokenGranter); + } + + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + // Stuff that can't be autowired + protected static class AuthorizationServerTypes extends AuthorizationServerConfigurerAdapter { + + @Autowired + private AuthorizationServerTokenServices tokenServices; + + @Autowired + private ClientDetailsService clientDetailsService; + + @Autowired + private OAuth2RequestFactory requestFactory; + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + assertTrue(tokenServices!=null && clientDetailsService!=null && requestFactory!=null); + } + + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerCustomClientDetails extends AuthorizationServerConfigurerAdapter + implements Runnable { + + @Autowired + private ApplicationContext context; + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + clients.withClientDetails(new InMemoryClientDetailsService()); + } + + @Override + public void run() { + assertNotNull(context.getBean(ClientDetailsService.class)); + } + + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerCustomUserDetails extends AuthorizationServerConfigurerAdapter + implements Runnable { + + @Autowired + private ApplicationContext context; + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.userDetailsService(userDetailsService()); + } + + private UserDetailsService userDetailsService() { + return new UserDetailsService() { + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return new User(username, "", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); + } + }; + } + + @Override + public void run() { + assertNotNull(context.getBean(UserDetailsService.class)); + } + + } + + // gh-638 + @EnableWebSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerSslEnabled extends AuthorizationServerConfigurerAdapter { + + @Override + public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { + security.sslOnly(); + } + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerCustomAuthenticationProvidersOnTokenEndpoint extends + AuthorizationServerConfigurerAdapter implements Runnable { + + @Autowired + private ApplicationContext context; + + @Override + public void configure(AuthorizationServerSecurityConfigurer security) + throws Exception { + security.addAuthenticationProvider(new AuthenticationManagerBeanDefinitionParser.NullAuthenticationProvider()); + security.addAuthenticationProvider(new TestingAuthenticationProvider()); + } + + @Override + public void run() { + FilterChainProxy springSecurityFilterChain = context.getBean(FilterChainProxy.class); + List filters = springSecurityFilterChain.getFilters("/oauth/token"); + BasicAuthenticationFilter basicAuthenticationFilter = null; + for (Filter filter : filters) { + if (filter instanceof BasicAuthenticationFilter) { + basicAuthenticationFilter = (BasicAuthenticationFilter) filter; + break; + } + } + + ProviderManager authenticationManager = (ProviderManager) ReflectionTestUtils.getField(basicAuthenticationFilter, "authenticationManager"); + boolean nullAuthenticationProviderFound = false; + boolean testingAuthenticationProviderFound = false; + boolean anonymousAuthenticationProviderFound = false; + for (AuthenticationProvider provider : authenticationManager.getProviders()) { + if (provider instanceof AuthenticationManagerBeanDefinitionParser.NullAuthenticationProvider) { + nullAuthenticationProviderFound = true; + } else if (provider instanceof TestingAuthenticationProvider) { + testingAuthenticationProviderFound = true; + } else if (provider instanceof AnonymousAuthenticationProvider) { + anonymousAuthenticationProviderFound = true; + } + } + + assertEquals(3, authenticationManager.getProviders().size()); + assertTrue(testingAuthenticationProviderFound); + assertTrue(anonymousAuthenticationProviderFound); + assertTrue(nullAuthenticationProviderFound); + } + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerDefaultAuthenticationProviderOnTokenEndpoint extends + AuthorizationServerConfigurerAdapter implements Runnable { + + @Autowired + private ApplicationContext context; + + @Override + public void run() { + FilterChainProxy springSecurityFilterChain = context.getBean(FilterChainProxy.class); + List filters = springSecurityFilterChain.getFilters("/oauth/token"); + BasicAuthenticationFilter basicAuthenticationFilter = null; + for (Filter filter : filters) { + if (filter instanceof BasicAuthenticationFilter) { + basicAuthenticationFilter = (BasicAuthenticationFilter) filter; + break; + } + } + + ProviderManager authenticationManager = (ProviderManager) ReflectionTestUtils.getField(basicAuthenticationFilter, "authenticationManager"); + boolean anonymousAuthenticationProviderFound = false; + boolean daoAuthenticationProviderFound = false; + + for (AuthenticationProvider provider : authenticationManager.getProviders()) { + if (provider instanceof DaoAuthenticationProvider) { + daoAuthenticationProviderFound = true; + } else if (provider instanceof AnonymousAuthenticationProvider) { + anonymousAuthenticationProviderFound = true; + } + } + + assertEquals(2, authenticationManager.getProviders().size()); + assertTrue(anonymousAuthenticationProviderFound); + assertTrue(daoAuthenticationProviderFound); + } + } + + @Configuration + @EnableWebMvcSecurity + @EnableAuthorizationServer + protected static class AuthorizationServerCustomAuthenticationEventPublisher extends + AuthorizationServerConfigurerAdapter implements Runnable { + + @Autowired + private ApplicationContext context; + private AuthenticationEventPublisher defaultAuthenticationEventPublisher = new DefaultAuthenticationEventPublisher(); + + @Override + public void configure(AuthorizationServerSecurityConfigurer security) + throws Exception { + security.authenticationEventPublisher(defaultAuthenticationEventPublisher); + } + + @Override + public void run() { + FilterChainProxy springSecurityFilterChain = context.getBean(FilterChainProxy.class); + List filters = springSecurityFilterChain.getFilters("/oauth/token"); + BasicAuthenticationFilter basicAuthenticationFilter = null; + for (Filter filter : filters) { + if (filter instanceof BasicAuthenticationFilter) { + basicAuthenticationFilter = (BasicAuthenticationFilter) filter; + break; + } + } + + AuthenticationManager authenticationManager = (AuthenticationManager) ReflectionTestUtils. + getField(basicAuthenticationFilter, "authenticationManager"); + AuthenticationEventPublisher authenticationEventPublisher = (AuthenticationEventPublisher) ReflectionTestUtils. + getField(authenticationManager, "eventPublisher"); + + assertTrue(authenticationEventPublisher == defaultAuthenticationEventPublisher); + } + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/ClientConfigurationTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/ClientConfigurationTests.java new file mode 100644 index 000000000..8714a4e79 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/ClientConfigurationTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2006-2011 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.config.annotation; + +import javax.annotation.Resource; + +import org.hamcrest.CoreMatchers; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.mock.web.MockServletContext; +import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; +import org.springframework.security.oauth2.client.OAuth2RestOperations; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter; +import org.springframework.security.oauth2.client.token.AccessTokenRequest; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; +import org.springframework.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * @author Dave Syer + * + */ +public class ClientConfigurationTests { + + @Test + public void testAuthCodeRedirect() throws Exception { + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.setServletContext(new MockServletContext()); + context.register(ClientContext.class); + context.refresh(); + MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).addFilters(new OAuth2ClientContextFilter()).build(); + mvc.perform(MockMvcRequestBuilders.get("/photos")) + .andExpect(MockMvcResultMatchers.status().isFound()) + .andExpect( + MockMvcResultMatchers.header().string("Location", + CoreMatchers.startsWith("https://example.com/authorize"))); + context.close(); + } + + @Controller + @Configuration + @EnableWebMvc + @Import(OAuth2ClientConfiguration.class) + protected static class ClientContext { + + @Resource + @Qualifier("accessTokenRequest") + private AccessTokenRequest accessTokenRequest; + + @RequestMapping("/photos") + @ResponseBody + public String photos() { + return restTemplate().getForObject("https://example.com/photos", String.class); + } + + @Bean + @Lazy + @Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES) + public OAuth2RestOperations restTemplate() { + AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails(); + resource.setClientId("client"); + resource.setAccessTokenUri("https://example.com/token"); + resource.setUserAuthorizationUri("https://example.com/authorize"); + return new OAuth2RestTemplate(resource, new DefaultOAuth2ClientContext(accessTokenRequest)); + } + + } + +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/Gh501EnableAuthorizationServerTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/Gh501EnableAuthorizationServerTests.java new file mode 100644 index 000000000..e3aa6a4ed --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/Gh501EnableAuthorizationServerTests.java @@ -0,0 +1,155 @@ +/* + * Copyright 2012-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.config.annotation; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.ClientRegistrationException; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.UnsupportedEncodingException; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * gh-501 + * + * @author Joe Grandja + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@WebAppConfiguration +public class Gh501EnableAuthorizationServerTests { + private static final String CLIENT_ID = "client-1234"; + private static final String CLIENT_SECRET = "secret-1234"; + private static BaseClientDetails client; + + @Autowired + WebApplicationContext context; + + @Autowired + FilterChainProxy springSecurityFilterChain; + + MockMvc mockMvc; + + static { + client = new BaseClientDetails(CLIENT_ID, null, "read,write", "client_credentials", null); + client.setClientSecret(CLIENT_SECRET); + } + + @Before + public void setup() { + mockMvc = MockMvcBuilders.webAppContextSetup(context).addFilters(springSecurityFilterChain).build(); + } + + @Test + public void clientAuthenticationFailsThenCustomAuthenticationEntryPointCalled() throws Exception { + mockMvc.perform(post("/oauth/token") + .param("grant_type", "client_credentials") + .header("Authorization", httpBasicCredentials(CLIENT_ID, "invalid-secret"))) + .andExpect(status().isUnauthorized()); + + verify(AuthorizationServerConfig.authenticationEntryPoint).commence( + any(HttpServletRequest.class), any(HttpServletResponse.class), any(AuthenticationException.class)); + } + + private String httpBasicCredentials(String userName, String password) { + String headerValue = "Basic "; + byte[] toEncode = null; + try { + toEncode = (userName + ":" + password).getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { } + headerValue += new String(Base64.encode(toEncode)); + return headerValue; + } + + @Configuration + @EnableAuthorizationServer + @EnableWebMvc + static class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { + private static AuthenticationEntryPoint authenticationEntryPoint = spy(new OAuth2AuthenticationEntryPoint()); + + @Override + public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { + security.authenticationEntryPoint(authenticationEntryPoint); + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + clients.withClientDetails(clientDetailsService()); + } + + @Bean + public ClientDetailsService clientDetailsService() { + return new ClientDetailsService() { + @Override + public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException { + return client; + } + }; + } + } + + @Configuration + @EnableWebSecurity + static class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().authenticated(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return NoOpPasswordEncoder.getInstance(); + } + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/Gh808EnableAuthorizationServerTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/Gh808EnableAuthorizationServerTests.java new file mode 100644 index 000000000..f0a9feacb --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/Gh808EnableAuthorizationServerTests.java @@ -0,0 +1,199 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.config.annotation; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.ClientRegistrationException; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import java.io.UnsupportedEncodingException; +import java.util.Arrays; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * gh-808 + * + * @author Joe Grandja + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@WebAppConfiguration +public class Gh808EnableAuthorizationServerTests { + private static final String CLIENT_ID = "acme"; + private static final String CLIENT_SECRET = "acmesecret"; + private static final String USER_ID = CLIENT_ID; + private static final String USER_SECRET = CLIENT_SECRET + "2"; + private static BaseClientDetails client; + private static UserDetails user; + + @Autowired + WebApplicationContext context; + + @Autowired + FilterChainProxy springSecurityFilterChain; + + MockMvc mockMvc; + + static { + client = new BaseClientDetails(CLIENT_ID, null, "read,write", "password,client_credentials", "ROLE_ADMIN", "https://example.com/oauth2callback"); + client.setClientSecret(CLIENT_SECRET); + user = new User(USER_ID, USER_SECRET, Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))); + } + + @Before + public void setup() { + mockMvc = MockMvcBuilders.webAppContextSetup(context).addFilters(springSecurityFilterChain).build(); + } + + @Test + public void clientAuthenticationFailsUsingUserCredentialsOnClientCredentialsGrantFlow() throws Exception { + mockMvc.perform(post("/oauth/token") + .param("grant_type", "client_credentials") + .header("Authorization", httpBasicCredentials(USER_ID, USER_SECRET))) + .andExpect(status().isUnauthorized()); + } + + @Test + public void clientAuthenticationFailsUsingUserCredentialsOnResourceOwnerPasswordGrantFlow() throws Exception { + mockMvc.perform(post("/oauth/token") + .param("grant_type", "password") + .param("client_id", CLIENT_ID) + .param("username", USER_ID) + .param("password", USER_SECRET) + .header("Authorization", httpBasicCredentials(USER_ID, USER_SECRET))) + .andExpect(status().isUnauthorized()); + } + + private String httpBasicCredentials(String userName, String password) { + String headerValue = "Basic "; + byte[] toEncode = null; + try { + toEncode = (userName + ":" + password).getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { } + headerValue += new String(Base64.encode(toEncode)); + return headerValue; + } + + @Configuration + @EnableAuthorizationServer + @EnableWebMvc + static class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { + + @Autowired + @Qualifier("authenticationManagerBean") + private AuthenticationManager authenticationManager; + + @Autowired + private UserDetailsService userDetailsService; + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + clients.withClientDetails(clientDetailsService()); + } + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints + .authenticationManager(this.authenticationManager) + .userDetailsService(this.userDetailsService); + } + + @Bean + public ClientDetailsService clientDetailsService() { + return new ClientDetailsService() { + @Override + public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException { + return client; + } + }; + } + } + + @Configuration + @EnableWebSecurity + static class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().authenticated(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .userDetailsService(userDetailsService()); + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + // Expose the Global AuthenticationManager + return super.authenticationManagerBean(); + } + + @Bean + public UserDetailsService userDetailsService() { + return new UserDetailsService() { + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return user; + } + }; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return NoOpPasswordEncoder.getInstance(); + } + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/ResourceServerConfigurationTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/ResourceServerConfigurationTests.java new file mode 100644 index 000000000..0136979c4 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/ResourceServerConfigurationTests.java @@ -0,0 +1,428 @@ +/* + * Copyright 2006-2011 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.config.annotation; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; + +import java.util.Collections; + +import javax.servlet.Filter; +import javax.servlet.http.HttpServletRequest; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockServletContext; +import org.springframework.security.authentication.AuthenticationDetailsSource; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.Authentication; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.TokenRequest; +import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails; +import org.springframework.security.oauth2.provider.authentication.TokenExtractor; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.filter.DelegatingFilterProxy; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * @author Dave Syer + * + */ +public class ResourceServerConfigurationTests { + + private static InMemoryTokenStore tokenStore = new InMemoryTokenStore(); + + private OAuth2AccessToken token; + private OAuth2Authentication authentication; + + @Rule + public ExpectedException expected = ExpectedException.none(); + + @Before + public void init() { + token = new DefaultOAuth2AccessToken("FOO"); + ClientDetails client = new BaseClientDetails("client", null, "read", "client_credentials", "ROLE_CLIENT"); + authentication = new OAuth2Authentication( + new TokenRequest(null, "client", null, "client_credentials").createOAuth2Request(client), null); + tokenStore.clear(); + } + + @Test + public void testDefaults() throws Exception { + tokenStore.storeAccessToken(token, authentication); + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.setServletContext(new MockServletContext()); + context.register(ResourceServerContext.class); + context.refresh(); + MockMvc mvc = buildMockMvc(context); + mvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.status().isUnauthorized()); + mvc.perform(MockMvcRequestBuilders.get("/").header("Authorization", "Bearer FOO")) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + context.close(); + } + + @Test + public void testWithAuthServer() throws Exception { + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.setServletContext(new MockServletContext()); + context.register(ResourceServerAndAuthorizationServerContext.class); + context.refresh(); + MockMvc mvc = buildMockMvc(context); + mvc.perform(MockMvcRequestBuilders.get("/")) + .andExpect(MockMvcResultMatchers.header().string("WWW-Authenticate", containsString("Bearer"))); + mvc.perform(MockMvcRequestBuilders.post("/oauth/token")) + .andExpect(MockMvcResultMatchers.header().string("WWW-Authenticate", containsString("Basic"))); + mvc.perform(MockMvcRequestBuilders.get("/oauth/authorize").accept(MediaType.TEXT_HTML)) + .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login")); + mvc.perform(MockMvcRequestBuilders.post("/oauth/token").header("Authorization", + "Basic " + new String(Base64.encode("client:secret".getBytes())))) + .andExpect(MockMvcResultMatchers.content().string(containsString("Missing grant type"))); + context.close(); + } + + @Test + public void testWithAuthServerCustomPath() throws Exception { + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.setServletContext(new MockServletContext()); + context.register(ResourceServerAndAuthorizationServerCustomPathContext.class); + context.refresh(); + MockMvc mvc = buildMockMvc(context); + mvc.perform(MockMvcRequestBuilders.get("/")) + .andExpect(MockMvcResultMatchers.header().string("WWW-Authenticate", containsString("Bearer"))); + mvc.perform(MockMvcRequestBuilders.post("/token")) + .andExpect(MockMvcResultMatchers.header().string("WWW-Authenticate", containsString("Basic"))); + mvc.perform(MockMvcRequestBuilders.get("/authorize").accept(MediaType.TEXT_HTML)) + .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login")); + mvc.perform(MockMvcRequestBuilders.post("/token").header("Authorization", + "Basic " + new String(Base64.encode("client:secret".getBytes())))) + .andExpect(MockMvcResultMatchers.content().string(containsString("Missing grant type"))); + context.close(); + } + + @Test + public void testWithAuthServerAndGlobalMethodSecurity() throws Exception { + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.setServletContext(new MockServletContext()); + context.register(ResourceServerAndAuthorizationServerContextAndGlobalMethodSecurity.class); + context.refresh(); + context.close(); + } + + @Test + public void testCustomTokenServices() throws Exception { + tokenStore.storeAccessToken(token, authentication); + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.setServletContext(new MockServletContext()); + context.register(TokenServicesContext.class); + context.refresh(); + MockMvc mvc = buildMockMvc(context); + mvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.status().isUnauthorized()); + mvc.perform(MockMvcRequestBuilders.get("/").header("Authorization", "Bearer FOO")) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + context.close(); + } + + @Test + public void testCustomTokenExtractor() throws Exception { + tokenStore.storeAccessToken(token, authentication); + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.setServletContext(new MockServletContext()); + context.register(TokenExtractorContext.class); + context.refresh(); + MockMvc mvc = buildMockMvc(context); + mvc.perform(MockMvcRequestBuilders.get("/").header("Authorization", "Bearer BAR")) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + context.close(); + } + + @Test + public void testCustomExpressionHandler() throws Exception { + tokenStore.storeAccessToken(token, authentication); + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.setServletContext(new MockServletContext()); + context.register(ExpressionHandlerContext.class); + context.refresh(); + MockMvc mvc = buildMockMvc(context); + expected.expect(IllegalArgumentException.class); + expected.expectMessage("#oauth2"); + mvc.perform(MockMvcRequestBuilders.get("/").header("Authorization", "Bearer FOO")) + .andExpect(MockMvcResultMatchers.status().isUnauthorized()); + context.close(); + } + + @Test + public void testCustomAuthenticationEntryPoint() throws Exception { + tokenStore.storeAccessToken(token, authentication); + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.setServletContext(new MockServletContext()); + context.register(AuthenticationEntryPointContext.class); + context.refresh(); + MockMvc mvc = buildMockMvc(context); + mvc.perform(MockMvcRequestBuilders.get("/").header("Authorization", "Bearer FOO")) + .andExpect(MockMvcResultMatchers.status().isFound()); + context.close(); + } + + @Test + public void testCustomAuthenticationDetailsSource() throws Exception { + tokenStore.storeAccessToken(token, authentication); + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.setServletContext(new MockServletContext()); + context.register(AuthenticationDetailsSourceContext.class); + context.refresh(); + MockMvc mvc = buildMockMvc(context); + mvc.perform(MockMvcRequestBuilders.get("/").header("Authorization", "Bearer FOO")) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + context.close(); + + OAuth2AuthenticationDetails authenticationDetails = (OAuth2AuthenticationDetails) authentication.getDetails(); + assertEquals("Basic", authenticationDetails.getTokenType()); + assertEquals("BAR", authenticationDetails.getTokenValue()); + } + + private MockMvc buildMockMvc(AnnotationConfigWebApplicationContext context) { + return MockMvcBuilders.webAppContextSetup(context) + .addFilters(new DelegatingFilterProxy(context.getBean("springSecurityFilterChain", Filter.class))) + .build(); + } + + @Configuration + @EnableResourceServer + @EnableWebSecurity + protected static class ResourceServerContext { + @Bean + public TokenStore tokenStore() { + return tokenStore; + } + } + + @Configuration + @EnableResourceServer + @EnableAuthorizationServer + @EnableWebSecurity + @EnableWebMvc + protected static class ResourceServerAndAuthorizationServerContext extends AuthorizationServerConfigurerAdapter { + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + clients.inMemory().withClient("client").secret("secret").scopes("scope"); + } + + @Configuration + protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter { + @Bean + public PasswordEncoder passwordEncoder() { + return NoOpPasswordEncoder.getInstance(); + } + } + } + + @Configuration + @EnableResourceServer + @EnableAuthorizationServer + @EnableWebSecurity + @EnableWebMvc + protected static class ResourceServerAndAuthorizationServerCustomPathContext + extends AuthorizationServerConfigurerAdapter { + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + clients.inMemory().withClient("client").secret("secret").scopes("scope"); + } + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints.pathMapping("/oauth/token", "/token"); + endpoints.pathMapping("/oauth/authorize", "/authorize"); + } + @Configuration + protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter { + @Bean + public PasswordEncoder passwordEncoder() { + return NoOpPasswordEncoder.getInstance(); + } + } + } + + @Configuration + @EnableResourceServer + @EnableAuthorizationServer + @EnableWebSecurity + @EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true) + protected static class ResourceServerAndAuthorizationServerContextAndGlobalMethodSecurity + extends AuthorizationServerConfigurerAdapter { + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + clients.inMemory(); + } + + @Autowired + public void setup(AuthenticationManagerBuilder builder) throws Exception { + builder.inMemoryAuthentication().withUser("user").password("password").roles("USER"); + } + } + + @Configuration + @EnableResourceServer + @EnableWebSecurity + protected static class AuthenticationEntryPointContext extends ResourceServerConfigurerAdapter { + + @Override + public void configure(HttpSecurity http) throws Exception { + http.authorizeRequests().anyRequest().authenticated(); + } + + @Override + public void configure(ResourceServerSecurityConfigurer resources) throws Exception { + resources.authenticationEntryPoint(authenticationEntryPoint()); + } + + private AuthenticationEntryPoint authenticationEntryPoint() { + return new LoginUrlAuthenticationEntryPoint("/login"); + } + + } + + @Configuration + @EnableResourceServer + @EnableWebSecurity + protected static class TokenExtractorContext extends ResourceServerConfigurerAdapter { + @Override + public void configure(ResourceServerSecurityConfigurer resources) throws Exception { + resources.tokenExtractor(new TokenExtractor() { + + @Override + public Authentication extract(HttpServletRequest request) { + return new PreAuthenticatedAuthenticationToken("FOO", "N/A"); + } + }).tokenStore(tokenStore()); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + http.authorizeRequests().anyRequest().access("#oauth2.isClient()"); + } + + @Bean + public TokenStore tokenStore() { + return tokenStore; + } + } + + @Configuration + @EnableResourceServer + @EnableWebSecurity + protected static class ExpressionHandlerContext extends ResourceServerConfigurerAdapter { + @Override + public void configure(ResourceServerSecurityConfigurer resources) throws Exception { + resources.expressionHandler(new DefaultWebSecurityExpressionHandler()); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + http.authorizeRequests().anyRequest().access("#oauth2.isClient()"); + } + + @Bean + public TokenStore tokenStore() { + return tokenStore; + } + } + + @Configuration + @EnableResourceServer + @EnableWebSecurity + protected static class TokenServicesContext { + + @Bean + protected ClientDetailsService clientDetailsService() { + InMemoryClientDetailsService service = new InMemoryClientDetailsService(); + service.setClientDetailsStore( + Collections.singletonMap("client", new BaseClientDetails("client", null, null, null, null))); + return service; + } + + @Bean + public DefaultTokenServices tokenServices() { + DefaultTokenServices tokenServices = new DefaultTokenServices(); + tokenServices.setTokenStore(tokenStore()); + tokenServices.setClientDetailsService(clientDetailsService()); + return tokenServices; + } + + @Bean + public TokenStore tokenStore() { + return tokenStore; + } + } + + @Configuration + @EnableResourceServer + @EnableWebSecurity + protected static class AuthenticationDetailsSourceContext extends ResourceServerConfigurerAdapter { + + @Override + public void configure(ResourceServerSecurityConfigurer resources) throws Exception { + resources.authenticationDetailsSource( + new AuthenticationDetailsSource() { + @Override + public OAuth2AuthenticationDetails buildDetails(HttpServletRequest request) { + request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, "Basic"); + request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, "BAR"); + return new OAuth2AuthenticationDetails(request); + } + }); + } + + @Bean + public TokenStore tokenStore() { + return tokenStore; + } + } + +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/TokenServicesMultipleBeansTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/TokenServicesMultipleBeansTests.java new file mode 100644 index 000000000..83b798f89 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/TokenServicesMultipleBeansTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.config.annotation; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.oauth2.config.annotation.TokenServicesMultipleBeansTests.BrokenOAuthApplication; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; +import org.springframework.security.oauth2.provider.token.ConsumerTokenServices; +import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +/** + * @author Dave Syer + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes=BrokenOAuthApplication.class) +@WebAppConfiguration +public class TokenServicesMultipleBeansTests { + + @Autowired(required=false) + private ResourceServerTokenServices tokenServices; + + @Autowired + private AuthorizationServerTokenServices authServerTokenServices; + + @Autowired + private ConsumerTokenServices consumerTokenServices; + + @Test + public void test() { + assertNull(tokenServices); + assertNotNull(authServerTokenServices); + assertNotNull(consumerTokenServices); + } + + @Configuration + @EnableAuthorizationServer + @EnableResourceServer + @EnableWebSecurity + protected static class BrokenOAuthApplication extends AuthorizationServerConfigurerAdapter { + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestAuthorizationServerBeanDefinitionParser.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerBeanDefinitionParserTests.java similarity index 53% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestAuthorizationServerBeanDefinitionParser.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerBeanDefinitionParserTests.java index 2c6fca207..452458cec 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestAuthorizationServerBeanDefinitionParser.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerBeanDefinitionParserTests.java @@ -4,18 +4,13 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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.config; - -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; -import java.util.List; +package org.springframework.security.oauth2.config.xml; import org.junit.After; import org.junit.Rule; @@ -26,27 +21,42 @@ import org.junit.runners.Parameterized.Parameters; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.GenericXmlApplicationContext; +import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; /** * @author Dave Syer * */ @RunWith(Parameterized.class) -public class TestAuthorizationServerBeanDefinitionParser { +public class AuthorizationServerBeanDefinitionParserTests { + + private static final String CHECK_TOKEN_CUSTOM_ENDPOINT_RESOURCE = "authorization-server-check-token-custom-endpoint"; private ConfigurableApplicationContext context; + private String resource; + @Rule public ExpectedException expected = ExpectedException.none(); @Parameters public static List parameters() { return Arrays.asList(new Object[] { "authorization-server-vanilla" }, - new Object[] { "authorization-server-extras" }, new Object[] { "authorization-server-types" }); + new Object[] { "authorization-server-extras" }, + new Object[] { "authorization-server-types" }, + new Object[] { "authorization-server-check-token" }, + new Object[] { "authorization-server-disable" }, + new Object[] { CHECK_TOKEN_CUSTOM_ENDPOINT_RESOURCE }); } - public TestAuthorizationServerBeanDefinitionParser(String resource) { - context = new GenericXmlApplicationContext(getClass(), resource + ".xml"); + public AuthorizationServerBeanDefinitionParserTests(String resource) { + this.resource = resource; + this.context = new GenericXmlApplicationContext(getClass(), resource + ".xml"); } @After @@ -61,4 +71,13 @@ public void testDefaults() { assertTrue(context.containsBeanDefinition("oauth2AuthorizationEndpoint")); } + @Test + public void testCheckTokenCustomEndpoint() { + if (!CHECK_TOKEN_CUSTOM_ENDPOINT_RESOURCE.equals(this.resource)) { + return; + } + FrameworkEndpointHandlerMapping frameworkEndpointHandlerMapping = context.getBean(FrameworkEndpointHandlerMapping.class); + assertNotNull(frameworkEndpointHandlerMapping); + assertEquals("/custom_check_token", frameworkEndpointHandlerMapping.getPath("/oauth/check_token")); + } } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerClientCredentialsPasswordInvalidXmlTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerClientCredentialsPasswordInvalidXmlTests.java new file mode 100644 index 000000000..cd9fc3b44 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerClientCredentialsPasswordInvalidXmlTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.config.xml; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.security.oauth2.config.xml.AuthorizationServerClientCredentialsPasswordValidXmlTests.httpBasicCredentials; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * gh-808 + * + * @author Joe Grandja + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = "authorization-server-client-credentials-password-invalid.xml") +@WebAppConfiguration +public class AuthorizationServerClientCredentialsPasswordInvalidXmlTests { + private static final String CLIENT_ID = "acme"; + private static final String CLIENT_SECRET = "secret"; + private static final String USER_ID = "acme"; + private static final String USER_SECRET = "password"; + + @Autowired + WebApplicationContext context; + + @Autowired + FilterChainProxy springSecurityFilterChain; + + MockMvc mockMvc; + + @Before + public void setup() { + mockMvc = MockMvcBuilders.webAppContextSetup(context).addFilters(springSecurityFilterChain).build(); + } + + @Test + public void clientAuthenticationPassesUsingUserCredentialsOnClientCredentialsGrantFlow() throws Exception { + mockMvc.perform(post("/oauth/token") + .param("grant_type", "client_credentials") + .header("Authorization", httpBasicCredentials(USER_ID, USER_SECRET))) + .andExpect(status().isOk()); + } + + @Test + public void clientAuthenticationPassesUsingUserCredentialsOnResourceOwnerPasswordGrantFlow() throws Exception { + mockMvc.perform(post("/oauth/token") + .param("grant_type", "password") + .param("client_id", CLIENT_ID) + .param("username", USER_ID) + .param("password", USER_SECRET) + .header("Authorization", httpBasicCredentials(USER_ID, USER_SECRET))) + .andExpect(status().isOk()); + } + +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerClientCredentialsPasswordValidXmlTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerClientCredentialsPasswordValidXmlTests.java new file mode 100644 index 000000000..f8acdc140 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerClientCredentialsPasswordValidXmlTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.config.xml; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.io.UnsupportedEncodingException; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * gh-808 + * + * @author Joe Grandja + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = "authorization-server-client-credentials-password-valid.xml") +@WebAppConfiguration +public class AuthorizationServerClientCredentialsPasswordValidXmlTests { + private static final String CLIENT_ID = "acme"; + private static final String USER_ID = "acme"; + private static final String USER_SECRET = "password"; + + @Autowired + WebApplicationContext context; + + @Autowired + FilterChainProxy springSecurityFilterChain; + + MockMvc mockMvc; + + @Before + public void setup() { + mockMvc = MockMvcBuilders.webAppContextSetup(context).addFilters(springSecurityFilterChain).build(); + } + + @Test + public void clientAuthenticationFailsUsingUserCredentialsOnClientCredentialsGrantFlow() throws Exception { + mockMvc.perform(post("/oauth/token") + .param("grant_type", "client_credentials") + .header("Authorization", httpBasicCredentials(USER_ID, USER_SECRET))) + .andExpect(status().isUnauthorized()); + } + + @Test + public void clientAuthenticationFailsUsingUserCredentialsOnResourceOwnerPasswordGrantFlow() throws Exception { + mockMvc.perform(post("/oauth/token") + .param("grant_type", "password") + .param("client_id", CLIENT_ID) + .param("username", USER_ID) + .param("password", USER_SECRET) + .header("Authorization", httpBasicCredentials(USER_ID, USER_SECRET))) + .andExpect(status().isUnauthorized()); + } + + static String httpBasicCredentials(String userName, String password) { + String headerValue = "Basic "; + byte[] toEncode = null; + try { + toEncode = (userName + ":" + password).getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { } + headerValue += new String(Base64.encode(toEncode)); + return headerValue; + } + +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestAuthorizationServerCustomGrantParser.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerCustomGrantParserTests.java similarity index 82% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestAuthorizationServerCustomGrantParser.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerCustomGrantParserTests.java index 15033ad91..e3ab6edee 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestAuthorizationServerCustomGrantParser.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerCustomGrantParserTests.java @@ -1,4 +1,4 @@ -package org.springframework.security.oauth2.config; +package org.springframework.security.oauth2.config.xml; import static org.junit.Assert.assertNotNull; @@ -9,11 +9,11 @@ import org.springframework.context.support.GenericXmlApplicationContext; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.CompositeTokenGranter; import org.springframework.security.oauth2.provider.TokenGranter; +import org.springframework.security.oauth2.provider.TokenRequest; -public class TestAuthorizationServerCustomGrantParser { +public class AuthorizationServerCustomGrantParserTests { private static String RESOURCE_NAME = "authorization-server-custom-grant.xml"; @@ -22,7 +22,7 @@ public class TestAuthorizationServerCustomGrantParser { @Rule public ExpectedException expected = ExpectedException.none(); - public TestAuthorizationServerCustomGrantParser() { + public AuthorizationServerCustomGrantParserTests() { context = new GenericXmlApplicationContext(getClass(), RESOURCE_NAME); } @@ -37,7 +37,7 @@ public static class CustomTestTokenGranter implements TokenGranter { public CustomTestTokenGranter() {} public OAuth2AccessToken grant(String grantType, - AuthorizationRequest authorizationRequest) { + TokenRequest tokenRequest) { if (grantType.equals("test-grant")) { return new DefaultOAuth2AccessToken("test"); } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerInvalidParserTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerInvalidParserTests.java new file mode 100644 index 000000000..372d310d1 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/AuthorizationServerInvalidParserTests.java @@ -0,0 +1,32 @@ +package org.springframework.security.oauth2.config.xml; + +import static org.junit.Assert.assertNotNull; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.GenericXmlApplicationContext; +import org.springframework.security.oauth2.provider.CompositeTokenGranter; +import org.springframework.security.oauth2.provider.TokenGranter; + +public class AuthorizationServerInvalidParserTests { + + private static String RESOURCE_NAME = "authorization-server-invalid.xml"; + + private ConfigurableApplicationContext context; + + @Rule + public ExpectedException expected = ExpectedException.none(); + + @Test + public void testCustomGrantRegistered() { + expected.expect(BeanDefinitionParsingException.class); + expected.expectMessage("ClientDetailsService"); + context = new GenericXmlApplicationContext(getClass(), RESOURCE_NAME); + TokenGranter granter = context.getBean(CompositeTokenGranter.class); + assertNotNull(granter); + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestClientDetailsServiceBeanDefinitionParser.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/ClientDetailsServiceBeanDefinitionParserTests.java similarity index 94% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestClientDetailsServiceBeanDefinitionParser.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/ClientDetailsServiceBeanDefinitionParserTests.java index 52cb95218..8bd436f7e 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestClientDetailsServiceBeanDefinitionParser.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/ClientDetailsServiceBeanDefinitionParserTests.java @@ -1,4 +1,4 @@ -package org.springframework.security.oauth2.config; +package org.springframework.security.oauth2.config.xml; import org.junit.Test; import org.junit.runner.RunWith; @@ -17,7 +17,7 @@ @ContextConfiguration @RunWith ( SpringJUnit4ClassRunner.class ) -public class TestClientDetailsServiceBeanDefinitionParser { +public class ClientDetailsServiceBeanDefinitionParserTests { @Autowired private ClientDetailsService clientDetailsService; @@ -84,7 +84,7 @@ public void testClientDetailsDefaultFlow() { assertNotNull(clientDetailsService); assertEquals("my-client-id-default-flow", clientDetails.getClientId()); assertEquals(1, clientDetails.getRegisteredRedirectUri().size()); - assertEquals("http://mycompany.com/", clientDetails.getRegisteredRedirectUri().iterator().next()); + assertEquals("https://secure.mycompany.com/", clientDetails.getRegisteredRedirectUri().iterator().next()); Set grantTypes = clientDetails.getAuthorizedGrantTypes(); assertNotNull(grantTypes); diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestInvalidResourceBeanDefinitionParser.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/InvalidResourceBeanDefinitionParserTests.java similarity index 70% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestInvalidResourceBeanDefinitionParser.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/InvalidResourceBeanDefinitionParserTests.java index 8f745b7e1..4f1d1cd5e 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestInvalidResourceBeanDefinitionParser.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/InvalidResourceBeanDefinitionParserTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.security.oauth2.config; +package org.springframework.security.oauth2.config.xml; import org.junit.After; import org.junit.Rule; @@ -29,7 +29,7 @@ * @author Dave Syer * */ -public class TestInvalidResourceBeanDefinitionParser { +public class InvalidResourceBeanDefinitionParserTests { private ConfigurableApplicationContext context; @@ -61,7 +61,21 @@ public void testMissingAuthorizationUriForImplicit() { public void testMissingAuthorizationUriForAuthorizationCode() { expected.expect(BeanDefinitionParsingException.class); expected.expectMessage("authorization URI must be supplied"); - loadContext("type='authorization_code' access-token-uri='http://somewhere.com/'"); + loadContext("type='authorization_code' access-token-uri='https://somewhere.com/'"); + } + + @Test + public void testMissingUsernameForPassword() { + expected.expect(BeanDefinitionParsingException.class); + expected.expectMessage("A username must be supplied on a resource element of type password"); + loadContext("type='password' access-token-uri='https://somewhere.com/'"); + } + + @Test + public void testMissingPasswordForPassword() { + expected.expect(BeanDefinitionParsingException.class); + expected.expectMessage("A password must be supplied on a resource element of type password"); + loadContext("type='password' username='admin' access-token-uri='https://somewhere.com/'"); } private void loadContext(String attributes) { @@ -69,7 +83,7 @@ private void loadContext(String attributes) { context = new GenericXmlApplicationContext(new ByteArrayResource(config .getBytes())); } - private static String HEADER = ""; + private static String HEADER = ""; private static String FOOTER = ""; private static String TEMPLATE = ""; } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestResourceBeanDefinitionParser.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/ResourceBeanDefinitionParserTests.java similarity index 74% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestResourceBeanDefinitionParser.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/ResourceBeanDefinitionParserTests.java index 96aff44b6..9eed8eec9 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/TestResourceBeanDefinitionParser.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/ResourceBeanDefinitionParserTests.java @@ -1,4 +1,4 @@ -package org.springframework.security.oauth2.config; +package org.springframework.security.oauth2.config.xml; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -14,13 +14,14 @@ import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitResourceDetails; +import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; import org.springframework.security.oauth2.common.AuthenticationScheme; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) -public class TestResourceBeanDefinitionParser { +public class ResourceBeanDefinitionParserTests { @Autowired @Qualifier("one") @@ -45,7 +46,11 @@ public class TestResourceBeanDefinitionParser { @Autowired @Qualifier("six") private AuthorizationCodeResourceDetails six; - + + @Autowired + @Qualifier("seven") + private ResourceOwnerPasswordResourceDetails seven; + @Autowired @Qualifier("template") private OAuth2RestTemplate template; @@ -54,7 +59,7 @@ public class TestResourceBeanDefinitionParser { public void testResourceFromNonPropertyFile() { assertEquals("my-client-id-non-property-file", one.getClientId()); assertEquals("my-client-secret-non-property-file", one.getClientSecret()); - assertEquals("http://somewhere.com/", one.getAccessTokenUri()); + assertEquals("https://somewhere.com/", one.getAccessTokenUri()); assertEquals(2, one.getScope().size()); assertEquals("[none, some]", one.getScope().toString()); } @@ -63,7 +68,7 @@ public void testResourceFromNonPropertyFile() { public void testResourceFromPropertyFile() { assertEquals("my-client-id-property-file", two.getClientId()); assertEquals("my-client-secret-property-file", two.getClientSecret()); - assertEquals("http://myhost.com/", two.getAccessTokenUri()); + assertEquals("https://myhost.com/", two.getAccessTokenUri()); assertEquals(2, two.getScope().size()); assertEquals("[none, all]", two.getScope().toString()); } @@ -72,8 +77,8 @@ public void testResourceFromPropertyFile() { public void testResourceWithRedirectUri() { assertEquals("my-client-id", three.getClientId()); assertNull(three.getClientSecret()); - assertEquals("http://somewhere.com/", three.getAccessTokenUri()); - assertEquals("http://anywhere.com/", three.getPreEstablishedRedirectUri()); + assertEquals("https://somewhere.com/", three.getAccessTokenUri()); + assertEquals("https://anywhere.com/", three.getPreEstablishedRedirectUri()); assertFalse(three.isUseCurrentUri()); } @@ -81,14 +86,14 @@ public void testResourceWithRedirectUri() { public void testResourceWithImplicitGrant() { assertEquals("my-client-id", four.getClientId()); assertNull(four.getClientSecret()); - assertEquals("http://somewhere.com/", four.getUserAuthorizationUri()); + assertEquals("https://somewhere.com/", four.getUserAuthorizationUri()); } @Test public void testResourceWithClientCredentialsGrant() { assertEquals("my-secret-id", five.getClientId()); assertEquals("secret", five.getClientSecret()); - assertEquals("http://somewhere.com/", five.getAccessTokenUri()); + assertEquals("https://somewhere.com/", five.getAccessTokenUri()); assertNotNull(template.getOAuth2ClientContext().getAccessTokenRequest()); } @@ -99,4 +104,12 @@ public void testResourceWithCurrentUriHint() { assertEquals(AuthenticationScheme.form, six.getClientAuthenticationScheme()); } + @Test + public void testResourceWithPasswordGrant() { + assertEquals("my-client-id", seven.getClientId()); + assertEquals("secret", seven.getClientSecret()); + assertEquals("https://somewhere.com/", seven.getAccessTokenUri()); + assertEquals("admin", seven.getUsername()); + assertEquals("long-and-strong", seven.getPassword()); + } } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/ResourceServerBeanDefinitionParserTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/ResourceServerBeanDefinitionParserTests.java new file mode 100644 index 000000000..cfae87560 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/xml/ResourceServerBeanDefinitionParserTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2006-2011 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.config.xml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.springframework.context.support.GenericXmlApplicationContext; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter; +import org.springframework.test.util.ReflectionTestUtils; + +/** + * @author Dave Syer + * + */ +public class ResourceServerBeanDefinitionParserTests { + + @Test + public void testDefaults() { + GenericXmlApplicationContext context = new GenericXmlApplicationContext( + getClass(), "resource-server-context.xml"); + assertTrue(context.containsBeanDefinition("oauth2ProviderFilter")); + assertTrue(context.containsBeanDefinition("anotherProviderFilter")); + assertTrue(context.containsBeanDefinition("thirdProviderFilter")); + context.close(); + } + + @Test + public void testAuthenticationManager() { + GenericXmlApplicationContext context = new GenericXmlApplicationContext( + getClass(), "resource-server-authmanager-context.xml"); + // System.err.println(Arrays.asList(context.getBeanDefinitionNames())); + assertTrue(context.containsBeanDefinition("oauth2ProviderFilter")); + OAuth2AuthenticationProcessingFilter filter = context.getBean(OAuth2AuthenticationProcessingFilter.class); + assertEquals(context.getBean(AuthenticationManager.class), ReflectionTestUtils.getField(filter, "authenticationManager")); + assertNotNull(ReflectionTestUtils.getField(filter, "tokenExtractor")); + context.close(); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/BaseJaxbMessageConverterTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/BaseJaxbMessageConverterTest.java index cf52a5bae..9342b5564 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/BaseJaxbMessageConverterTest.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/BaseJaxbMessageConverterTest.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/TestJaxbOAuth2AccessTokenMessageConverter.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2AccessTokenMessageConverterTests.java similarity index 97% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/TestJaxbOAuth2AccessTokenMessageConverter.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2AccessTokenMessageConverterTests.java index 99da1a4ba..f2a8e17e2 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/TestJaxbOAuth2AccessTokenMessageConverter.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2AccessTokenMessageConverterTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -35,7 +35,7 @@ * */ @PrepareForTest(JaxbOAuth2AccessToken.class) -public class TestJaxbOAuth2AccessTokenMessageConverter extends BaseJaxbMessageConverterTest { +public class JaxbOAuth2AccessTokenMessageConverterTests extends BaseJaxbMessageConverterTest { private JaxbOAuth2AccessTokenMessageConverter converter; private DefaultOAuth2AccessToken accessToken; diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/TestJaxbOAuth2ExceptionMessageConverter.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2ExceptionMessageConverterTests.java similarity index 90% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/TestJaxbOAuth2ExceptionMessageConverter.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2ExceptionMessageConverterTests.java index cb8930de7..bc146c02a 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/TestJaxbOAuth2ExceptionMessageConverter.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/http/converter/jaxb/JaxbOAuth2ExceptionMessageConverterTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -24,16 +24,7 @@ import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import org.springframework.security.oauth2.common.exceptions.InvalidClientException; -import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; -import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; -import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; -import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; -import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException; -import org.springframework.security.oauth2.common.exceptions.UnauthorizedClientException; -import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException; -import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException; +import org.springframework.security.oauth2.common.exceptions.*; /** * @@ -42,7 +33,7 @@ */ @RunWith(PowerMockRunner.class) @PrepareForTest({ System.class, JaxbOAuth2AccessToken.class }) -public class TestJaxbOAuth2ExceptionMessageConverter extends BaseJaxbMessageConverterTest { +public class JaxbOAuth2ExceptionMessageConverterTests extends BaseJaxbMessageConverterTest { private JaxbOAuth2ExceptionMessageConverter converter; private static String DETAILS = "some detail"; diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/AuthorizationRequestTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/AuthorizationRequestTests.java new file mode 100644 index 000000000..5f9ea7a82 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/AuthorizationRequestTests.java @@ -0,0 +1,179 @@ +/* + * Copyright 2002-2011 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.provider; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.common.util.SerializationUtils; +import org.springframework.util.StringUtils; + +/** + * @author Dave Syer + * @author Christian Hilmersson + * + */ +public class AuthorizationRequestTests { + + private Map parameters; + + @Before + public void prepare() { + parameters = new HashMap(); + parameters.put("client_id", "theClient"); + parameters.put("state", "XYZ123"); + parameters.put("redirect_uri", "https://callistaenterprise.se/"); + } + + @Test + public void testApproval() throws Exception { + AuthorizationRequest authorizationRequest = createFromParameters(parameters); + assertFalse(authorizationRequest.isApproved()); + authorizationRequest.setApproved(true); + assertTrue(authorizationRequest.isApproved()); + } + + /** + * Ensure that setting the scope does not alter the original request parameters. + * + * @throws Exception + */ + @Test + public void testScopeNotSetInParameters() throws Exception { + parameters.put("scope", "read,write"); + AuthorizationRequest authorizationRequest = createFromParameters(parameters); + authorizationRequest.setScope(StringUtils.commaDelimitedListToSet("foo,bar")); + assertFalse(authorizationRequest.getRequestParameters().get(OAuth2Utils.SCOPE).contains("bar")); + assertFalse(authorizationRequest.getRequestParameters().get(OAuth2Utils.SCOPE).contains("foo")); + } + + /** + * Ensure that setting a single value scope which contains spaces + * will result in exploding multiple scopes. + */ + @Test + public void testSpaceSeparatedScopesAreExploded() throws Exception { + AuthorizationRequest authorizationRequest = createFromParameters(parameters); + String multiScope = "foo bar"; + authorizationRequest.setScope(Collections.singleton(multiScope)); + assertEquals(authorizationRequest.getScope().size(), 2); + assertTrue(authorizationRequest.getScope().containsAll(Arrays.asList("foo", "bar"))); + assertFalse(authorizationRequest.getScope().contains(multiScope)); + } + + /** + * Ensure that setting a single value scope which contains commas + * will result in exploding multiple scopes. + */ + @Test + public void testCommaInScopeIsAllowed() throws Exception { + AuthorizationRequest authorizationRequest = createFromParameters(parameters); + String multiScope = "foo,bar"; + authorizationRequest.setScope(Collections.singleton(multiScope)); + assertEquals(authorizationRequest.getScope().size(), 1); + assertTrue(authorizationRequest.getScope().contains(multiScope)); + } + + @Test + public void testClientIdNotOverwitten() throws Exception { + AuthorizationRequest authorizationRequest = new AuthorizationRequest("client", Arrays.asList("read")); + parameters = new HashMap(); + parameters.put("scope", "write"); + authorizationRequest.setRequestParameters(parameters); + + assertEquals("client", authorizationRequest.getClientId()); + assertEquals(1, authorizationRequest.getScope().size()); + assertTrue(authorizationRequest.getScope().contains("read")); + assertFalse(authorizationRequest.getRequestParameters().get(OAuth2Utils.SCOPE).contains("read")); + } + + @Test + public void testScopeWithSpace() throws Exception { + parameters.put("scope", "bar foo"); + AuthorizationRequest authorizationRequest = createFromParameters(parameters); + authorizationRequest.setScope(Collections.singleton("foo bar")); + assertEquals("bar foo", authorizationRequest.getRequestParameters().get(OAuth2Utils.SCOPE)); + } + + /** + * Tests that the construction of an AuthorizationRequest objects using + * a parameter Map maintains a sorted order of the scope. + */ + @Test + public void testScopeSortedOrder() { + // Arbitrary scope set + String scopeString = "AUTHORITY_A AUTHORITY_X AUTHORITY_B AUTHORITY_C AUTHORITY_D " + + "AUTHORITY_Y AUTHORITY_V AUTHORITY_ZZ AUTHORITY_DYV AUTHORITY_ABC AUTHORITY_BA " + + "AUTHORITY_AV AUTHORITY_AB AUTHORITY_CDA AUTHORITY_ABCD"; + // Create correctly sorted scope string + Set sortedSet = OAuth2Utils.parseParameterList(scopeString); + assertTrue(sortedSet instanceof SortedSet); + String sortedScopeString = OAuth2Utils.formatParameterList(sortedSet); + + parameters.put("scope", scopeString); + AuthorizationRequest authorizationRequest = createFromParameters(parameters); + authorizationRequest.setScope(sortedSet); + + // Assert that the scope parameter is still sorted + + String fromAR = OAuth2Utils.formatParameterList(authorizationRequest.getScope()); + + assertEquals(sortedScopeString, fromAR); + } + + @Test + public void testRedirectUriDefaultsToMap() { + parameters.put("scope", "one two"); + AuthorizationRequest authorizationRequest = createFromParameters(parameters); + + assertEquals("XYZ123", authorizationRequest.getState()); + assertEquals("theClient", authorizationRequest.getClientId()); + assertEquals("https://callistaenterprise.se/", authorizationRequest.getRedirectUri()); + assertEquals("https://callistaenterprise.se/", authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI)); + assertEquals("[one, two]", authorizationRequest.getScope().toString()); + } + + @Test + public void testSerialization() { + AuthorizationRequest authorizationRequest = createFromParameters(parameters); + AuthorizationRequest other = SerializationUtils.deserialize( + SerializationUtils.serialize(authorizationRequest)); + assertEquals(authorizationRequest, other); + } + + private AuthorizationRequest createFromParameters(Map authorizationParameters) { + AuthorizationRequest request = new AuthorizationRequest(authorizationParameters, Collections. emptyMap(), + authorizationParameters.get(OAuth2Utils.CLIENT_ID), + OAuth2Utils.parseParameterList(authorizationParameters.get(OAuth2Utils.SCOPE)), null, + null, false, authorizationParameters.get(OAuth2Utils.STATE), + authorizationParameters.get(OAuth2Utils.REDIRECT_URI), + OAuth2Utils.parseParameterList(authorizationParameters.get(OAuth2Utils.RESPONSE_TYPE))); + return request; + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/OAuth2AuthenticationTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/OAuth2AuthenticationTests.java new file mode 100644 index 000000000..e2c0aadbd --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/OAuth2AuthenticationTests.java @@ -0,0 +1,89 @@ +package org.springframework.security.oauth2.provider; + +import java.util.Arrays; +import java.util.Collections; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails; +import org.springframework.test.annotation.Rollback; +import org.springframework.util.SerializationUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class OAuth2AuthenticationTests { + + private OAuth2Request request = RequestTokenFactory.createOAuth2Request(null, "id", null, false, + Collections.singleton("read"), null, null, null, null); + + private UsernamePasswordAuthenticationToken userAuthentication = new UsernamePasswordAuthenticationToken("foo", + "bar", Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))); + + @Test + @Rollback + public void testIsAuthenticated() { + request = RequestTokenFactory.createOAuth2Request("id", true, Collections.singleton("read")); + OAuth2Authentication authentication = new OAuth2Authentication(request, userAuthentication); + assertTrue(authentication.isAuthenticated()); + } + + @Test + public void testGetCredentials() { + OAuth2Authentication authentication = new OAuth2Authentication(request, userAuthentication); + assertEquals("", authentication.getCredentials()); + } + + @Test + public void testGetPrincipal() { + OAuth2Authentication authentication = new OAuth2Authentication(request, userAuthentication); + assertEquals(userAuthentication.getPrincipal(), authentication.getPrincipal()); + } + + @Test + public void testIsClientOnly() { + OAuth2Authentication authentication = new OAuth2Authentication(request, null); + assertTrue(authentication.isClientOnly()); + } + + @Test + public void testJsonSerialization() throws Exception { + System.err + .println(new ObjectMapper().writeValueAsString(new OAuth2Authentication(request, userAuthentication))); + } + + @Test + public void testSerialization() { + OAuth2Authentication holder = new OAuth2Authentication( + new AuthorizationRequest("client", Arrays.asList("read")).createOAuth2Request(), + new UsernamePasswordAuthenticationToken("user", "pwd")); + OAuth2Authentication other = (OAuth2Authentication) SerializationUtils.deserialize(SerializationUtils + .serialize(holder)); + assertEquals(holder, other); + } + + @Test + public void testSerializationWithDetails() { + OAuth2Authentication holder = new OAuth2Authentication( + new AuthorizationRequest("client", Arrays.asList("read")).createOAuth2Request(), + new UsernamePasswordAuthenticationToken("user", "pwd")); + holder.setDetails(new OAuth2AuthenticationDetails(new MockHttpServletRequest())); + OAuth2Authentication other = (OAuth2Authentication) SerializationUtils.deserialize(SerializationUtils + .serialize(holder)); + assertEquals(holder, other); + } + + // gh-573 + @Test + public void testEraseCredentialsUserAuthentication() { + OAuth2Authentication authentication = new OAuth2Authentication(request, userAuthentication); + authentication.eraseCredentials(); + assertNull(authentication.getUserAuthentication().getCredentials()); + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/OAuth2RequestTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/OAuth2RequestTests.java new file mode 100644 index 000000000..1462e82af --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/OAuth2RequestTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 20013-2014 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.provider; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; + +import java.io.Serializable; +import java.util.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.common.util.SerializationUtils; + +/** + * @author Dave Syer + * + */ +public class OAuth2RequestTests { + + private Map parameters; + + @Before + public void prepare() { + parameters = new HashMap(); + parameters.put("client_id", "theClient"); + } + + @Test + public void testImplicitGrantType() throws Exception { + parameters.put("response_type", "token"); + OAuth2Request authorizationRequest = createFromParameters(parameters); + assertEquals("implicit", authorizationRequest.getGrantType()); + } + + @Test + public void testOtherGrantType() throws Exception { + parameters.put("grant_type", "password"); + OAuth2Request authorizationRequest = createFromParameters(parameters); + assertEquals("password", authorizationRequest.getGrantType()); + } + + // gh-724 + @Test + public void testResourceIdsConstructorAssignment() { + Set resourceIds = new HashSet(Arrays.asList("resourceId-1", "resourceId-2")); + OAuth2Request request = new OAuth2Request( + Collections.emptyMap(), "clientId", Collections.emptyList(), + false, Collections.emptySet(), resourceIds, "redirectUri", Collections.emptySet(), + Collections.emptyMap()); + assertNotSame("resourceIds are the same", resourceIds, request.getResourceIds()); + } + + private OAuth2Request createFromParameters(Map parameters) { + OAuth2Request request = RequestTokenFactory.createOAuth2Request(parameters, + parameters.get(OAuth2Utils.CLIENT_ID), false, + OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE))); + return request; + } + + // gh-812 + @Test + public void testRequestParametersReAssignmentWithSerialization() { + Map requestParameters = new HashMap(); + requestParameters.put("key", "value"); + + OAuth2Request request = new OAuth2Request( + requestParameters, "clientId", Collections.emptyList(), + false, Collections.emptySet(), Collections.emptySet(), "redirectUri", + Collections.emptySet(), Collections.emptyMap()); + + OAuth2Request request2 = new OAuth2Request( + Collections.emptyMap(), "clientId", Collections.emptyList(), + false, Collections.emptySet(), Collections.emptySet(), "redirectUri", + Collections.emptySet(), Collections.emptyMap()); + request2.setRequestParameters(request.getRequestParameters()); + + byte[] serializedRequest = SerializationUtils.serialize(request); + byte[] serializedRequest2 = SerializationUtils.serialize(request2); + + assertEquals(serializedRequest.length, serializedRequest2.length); + } + + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/RequestTokenFactory.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/RequestTokenFactory.java new file mode 100644 index 000000000..0249795fc --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/RequestTokenFactory.java @@ -0,0 +1,54 @@ +/* + * Cloud Foundry 2012.02.03 Beta + * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + */ + +package org.springframework.security.oauth2.provider; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.security.core.GrantedAuthority; + +/** + * Factory for tests to create OAuth2Request objects. + * + * @author Dave Syer + * + */ +public class RequestTokenFactory { + + public static OAuth2Request createOAuth2Request(Map requestParameters, String clientId, + Collection authorities, boolean approved, Collection scope, + Set resourceIds, String redirectUri, Set responseTypes, + Map extensionProperties) { + return new OAuth2Request(requestParameters, clientId, authorities, approved, scope == null ? null + : new LinkedHashSet(scope), resourceIds, redirectUri, responseTypes, extensionProperties); + } + + public static OAuth2Request createOAuth2Request(String clientId, boolean approved) { + return createOAuth2Request(clientId, approved, null); + } + + public static OAuth2Request createOAuth2Request(String clientId, boolean approved, Collection scope) { + return createOAuth2Request(Collections. emptyMap(), clientId, approved, scope); + } + + public static OAuth2Request createOAuth2Request(Map parameters, String clientId, boolean approved, + Collection scope) { + return createOAuth2Request(parameters, clientId, null, approved, scope, null, null, null, null); + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestDefaultAuthorizationRequest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestDefaultAuthorizationRequest.java deleted file mode 100644 index 43c91ab3c..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestDefaultAuthorizationRequest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2002-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.provider; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.springframework.security.oauth2.provider.AuthorizationRequest.REDIRECT_URI; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.SortedSet; - -import junit.framework.Assert; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.security.oauth2.common.util.OAuth2Utils; -import org.springframework.util.StringUtils; - -/** - * @author Dave Syer - * @author Christian Hilmersson - * - */ -public class TestDefaultAuthorizationRequest { - - private Map parameters; - - @Before - public void prepare() { - parameters = new HashMap(); - parameters.put("client_id", "theClient"); - parameters.put("state", "XYZ123"); - parameters.put("redirect_uri", "http://www.callistaenterprise.se/"); - } - - @Test - public void testApproval() throws Exception { - DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest(parameters); - assertFalse(authorizationRequest.isApproved()); - authorizationRequest.setApproved(true); - assertTrue(authorizationRequest.isApproved()); - } - - @Test - public void testScopeSetInParameters() throws Exception { - DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest(parameters); - authorizationRequest.setScope(StringUtils.commaDelimitedListToSet("foo,bar")); - assertEquals("bar foo", authorizationRequest.getAuthorizationParameters().get(AuthorizationRequest.SCOPE)); - } - - @Test - public void testScopeWithSpace() throws Exception { - DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest(parameters); - authorizationRequest.setScope(Collections.singleton("foo bar")); - assertEquals("bar foo", authorizationRequest.getAuthorizationParameters().get(AuthorizationRequest.SCOPE)); - } - - /** - * Tests that the construction of an AuthorizationRequest objects using - * a parameter Map maintains a sorted order of the scope. - */ - @Test - public void testScopeSortedOrder() { - // Arbitrary scope set - String scopeString = "AUTHORITY_A AUTHORITY_X AUTHORITY_B AUTHORITY_C AUTHORITY_D " + - "AUTHORITY_Y AUTHORITY_V AUTHORITY_ZZ AUTHORITY_DYV AUTHORITY_ABC AUTHORITY_BA " + - "AUTHORITY_AV AUTHORITY_AB AUTHORITY_CDA AUTHORITY_ABCD"; - // Create correctly sorted scope string - Set sortedSet = OAuth2Utils.parseParameterList(scopeString); - Assert.assertTrue(sortedSet instanceof SortedSet); - String sortedScopeString = OAuth2Utils.formatParameterList(sortedSet); - - parameters.put("scope", scopeString); - AuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest(parameters); - - // Assert that both the scope parameter and the scope Set of - // the constructed AuthorizationRequest are sorted - Assert.assertEquals(sortedScopeString, OAuth2Utils.formatParameterList(authorizationRequest.getScope())); - Assert.assertEquals(sortedScopeString, authorizationRequest.getAuthorizationParameters().get("scope")); - } - - @Test - public void testRedirectUriDefaultsToMap() { - parameters.put("scope", "one two"); - DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest(parameters); - assertEquals("XYZ123", authorizationRequest.getState()); - assertEquals("theClient", authorizationRequest.getClientId()); - assertEquals("http://www.callistaenterprise.se/", authorizationRequest.getRedirectUri()); - assertEquals("http://www.callistaenterprise.se/", authorizationRequest.getAuthorizationParameters().get(REDIRECT_URI)); - assertEquals("[one, two]", authorizationRequest.getScope().toString()); - } - -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestDefaultAuthorizationRequestManager.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestDefaultAuthorizationRequestManager.java deleted file mode 100644 index 27c1159b6..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestDefaultAuthorizationRequestManager.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2006-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - - -package org.springframework.security.oauth2.provider; - -import static org.junit.Assert.assertEquals; - -import java.util.Collections; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; - -/** - * @author Dave Syer - * - */ -public class TestDefaultAuthorizationRequestManager { - - private BaseClientDetails client = new BaseClientDetails(); - - private DefaultAuthorizationRequestManager factory = new DefaultAuthorizationRequestManager(new ClientDetailsService() { - public ClientDetails loadClientByClientId(String clientId) throws OAuth2Exception { - return client; - } - }); - - @Before - public void start() { - client.setClientId("foo"); - client.setScope(Collections.singleton("bar")); - } - - @Test - public void testCreateAuthorizationRequest() { - AuthorizationRequest request = factory.createAuthorizationRequest(Collections.singletonMap("client_id", "foo")); - assertEquals("foo", request.getClientId()); - } - - @Test - public void testCreateAuthorizationRequestWithDefaultScopes() { - AuthorizationRequest request = factory.createAuthorizationRequest(Collections.singletonMap("client_id", "foo")); - assertEquals("[bar]", request.getScope().toString()); - } - -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestOAuth2Authentication.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestOAuth2Authentication.java deleted file mode 100644 index d42507c64..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestOAuth2Authentication.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.springframework.security.oauth2.provider; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; -import java.util.Collections; - -import org.codehaus.jackson.map.ObjectMapper; -import org.junit.Test; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -public class TestOAuth2Authentication { - - private DefaultAuthorizationRequest request = new DefaultAuthorizationRequest("id", Arrays.asList("read")); - - private UsernamePasswordAuthenticationToken userAuthentication = new UsernamePasswordAuthenticationToken("foo", - "bar", Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))); - - @Test - public void testIsAuthenticated() { - request.setApproved(true); - OAuth2Authentication authentication = new OAuth2Authentication(request, userAuthentication); - assertTrue(authentication.isAuthenticated()); - } - - @Test - public void testGetCredentials() { - OAuth2Authentication authentication = new OAuth2Authentication(request, userAuthentication); - assertEquals("", authentication.getCredentials()); - } - - @Test - public void testGetPrincipal() { - OAuth2Authentication authentication = new OAuth2Authentication(request, userAuthentication); - assertEquals(userAuthentication.getPrincipal(), authentication.getPrincipal()); - } - - @Test - public void testIsClientOnly() { - OAuth2Authentication authentication = new OAuth2Authentication(request, null); - assertTrue(authentication.isClientOnly()); - } - - @Test - public void testJsonSerialization() throws Exception { - System.err.println(new ObjectMapper().writeValueAsString(new OAuth2Authentication(request, userAuthentication))); - } - -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/AbstractTestApprovalStore.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/AbstractTestApprovalStore.java new file mode 100644 index 000000000..034b25563 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/AbstractTestApprovalStore.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.approval; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.oauth2.provider.approval.Approval.ApprovalStatus; + +/** + * @author Dave Syer + * + */ +public abstract class AbstractTestApprovalStore { + + private ApprovalStore store; + + @Before + public void setupStore() { + store = getApprovalStore(); + } + + protected abstract ApprovalStore getApprovalStore(); + + protected boolean addApprovals(Collection approvals) { + return store.addApprovals(approvals); + } + + @Test + public void testAddEmptyCollection() { + assertTrue(addApprovals(Arrays. asList())); + assertEquals(0, store.getApprovals("foo", "bar").size()); + } + + @Test + public void testAddDifferentScopes() { + assertTrue(addApprovals(Arrays. asList(new Approval("user", "client", "read", 1000, + ApprovalStatus.APPROVED), new Approval("user", "client", "write", 1000, ApprovalStatus.APPROVED)))); + assertEquals(2, store.getApprovals("user", "client").size()); + } + + @Test + public void testIdempotentAdd() { + assertTrue(addApprovals(Arrays. asList(new Approval("user", "client", "read", 1000, + ApprovalStatus.APPROVED), new Approval("user", "client", "write", 1000, ApprovalStatus.APPROVED)))); + assertTrue(addApprovals(Arrays. asList(new Approval("user", "client", "read", 1000, + ApprovalStatus.APPROVED), new Approval("user", "client", "write", 1000, ApprovalStatus.APPROVED)))); + assertEquals(2, store.getApprovals("user", "client").size()); + } + + @Test + public void testAddDifferentClients() { + assertTrue(addApprovals(Arrays. asList(new Approval("user", "client", "read", 1000, + ApprovalStatus.APPROVED), new Approval("user", "other", "write", 1000, ApprovalStatus.APPROVED)))); + assertEquals(1, store.getApprovals("user", "client").size()); + assertEquals(1, store.getApprovals("user", "other").size()); + } + + @Test + public void testVanillaRevoke() { + Approval approval1 = new Approval("user", "client", "read", 1000, ApprovalStatus.APPROVED); + Approval approval2 = new Approval("user", "client", "write", 1000, ApprovalStatus.APPROVED); + assertTrue(addApprovals(Arrays. asList(approval1, approval2))); + store.revokeApprovals(Arrays.asList(approval1)); + assertEquals(getExpectedNumberOfApprovalsAfterRevoke(), store.getApprovals("user", "client").size()); + } + + protected int getExpectedNumberOfApprovalsAfterRevoke() { + return 1; + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/ApprovalStoreUserApprovalHandlerTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/ApprovalStoreUserApprovalHandlerTests.java new file mode 100644 index 000000000..cc1d6c0b4 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/ApprovalStoreUserApprovalHandlerTests.java @@ -0,0 +1,155 @@ +package org.springframework.security.oauth2.provider.approval; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; + +public class ApprovalStoreUserApprovalHandlerTests { + + private ApprovalStoreUserApprovalHandler handler = new ApprovalStoreUserApprovalHandler(); + + private InMemoryApprovalStore store = new InMemoryApprovalStore(); + + private InMemoryClientDetailsService clientDetailsService = new InMemoryClientDetailsService(); + + private Authentication userAuthentication; + + @Before + public void init() { + handler.setApprovalStore(store); + InMemoryClientDetailsService clientDetailsService = new InMemoryClientDetailsService(); + Map map = new HashMap(); + map.put("client", new BaseClientDetails("client", null, "read,write", "authorization_code", null)); + clientDetailsService.setClientDetailsStore(map); + handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService)); + userAuthentication = new UsernamePasswordAuthenticationToken("user", "N/A", + AuthorityUtils.commaSeparatedStringToAuthorityList("USER")); + } + + @Test + public void testApprovalLongExpiry() throws Exception { + handler.setApprovalExpiryInSeconds(365*24*60*60); + AuthorizationRequest authorizationRequest = new AuthorizationRequest("client", Arrays.asList("read")); + authorizationRequest.setApprovalParameters(Collections.singletonMap("scope.read", "approved")); + AuthorizationRequest result = handler.updateAfterApproval(authorizationRequest, userAuthentication); + assertTrue(handler.isApproved(result, userAuthentication)); + } + + @Test + public void testExplicitlyApprovedScopes() { + AuthorizationRequest authorizationRequest = new AuthorizationRequest("client", Arrays.asList("read")); + authorizationRequest.setApprovalParameters(Collections.singletonMap("scope.read", "approved")); + AuthorizationRequest result = handler.updateAfterApproval(authorizationRequest, userAuthentication); + assertTrue(handler.isApproved(result, userAuthentication)); + assertEquals(1, store.getApprovals("user", "client").size()); + assertEquals(1, result.getScope().size()); + assertTrue(result.isApproved()); + } + + @Test + public void testImplicitlyDeniedScope() { + AuthorizationRequest authorizationRequest = new AuthorizationRequest("client", Arrays.asList("read", "write")); + authorizationRequest.setApprovalParameters(Collections.singletonMap("scope.read", "approved")); + AuthorizationRequest result = handler.updateAfterApproval(authorizationRequest, userAuthentication); + assertTrue(handler.isApproved(result, userAuthentication)); + Collection approvals = store.getApprovals("user", "client"); + assertEquals(2, approvals.size()); + approvals.contains(new Approval("user", "client", "read", new Date(), Approval.ApprovalStatus.APPROVED)); + approvals.contains(new Approval("user", "client", "write", new Date(), Approval.ApprovalStatus.DENIED)); + assertEquals(1, result.getScope().size()); + } + + @Test + public void testExplicitlyPreapprovedScopes() { + store.addApprovals(Arrays.asList(new Approval("user", "client", "read", new Date( + System.currentTimeMillis() + 10000), Approval.ApprovalStatus.APPROVED))); + AuthorizationRequest authorizationRequest = new AuthorizationRequest("client", Arrays.asList("read")); + AuthorizationRequest result = handler.checkForPreApproval(authorizationRequest, userAuthentication); + assertTrue(result.isApproved()); + } + + @Test + public void testExplicitlyUnapprovedScopes() { + store.addApprovals(Arrays.asList(new Approval("user", "client", "read", new Date( + System.currentTimeMillis() + 10000), Approval.ApprovalStatus.DENIED))); + AuthorizationRequest authorizationRequest = new AuthorizationRequest("client", Arrays.asList("read")); + AuthorizationRequest result = handler.checkForPreApproval(authorizationRequest, userAuthentication); + assertFalse(result.isApproved()); + } + + @Test + public void testAutoapprovedScopes() { + handler.setClientDetailsService(clientDetailsService); + BaseClientDetails client = new BaseClientDetails("client", null, "read", "authorization_code", null); + client.setAutoApproveScopes(new HashSet(Arrays.asList("read"))); + clientDetailsService.setClientDetailsStore(Collections.singletonMap("client", client)); + AuthorizationRequest authorizationRequest = new AuthorizationRequest("client", Arrays.asList("read")); + AuthorizationRequest result = handler.checkForPreApproval(authorizationRequest, userAuthentication); + assertTrue(result.isApproved()); + } + + @Test + public void testAutoapprovedWildcardScopes() { + handler.setClientDetailsService(clientDetailsService); + BaseClientDetails client = new BaseClientDetails("client", null, "read", "authorization_code", null); + client.setAutoApproveScopes(new HashSet(Arrays.asList(".*"))); + clientDetailsService.setClientDetailsStore(Collections.singletonMap("client", client)); + AuthorizationRequest authorizationRequest = new AuthorizationRequest("client", Arrays.asList("read")); + AuthorizationRequest result = handler.checkForPreApproval(authorizationRequest, userAuthentication); + assertTrue(result.isApproved()); + } + + @Test + public void testApprovalsAddedForAutoapprovedScopes() { + handler.setClientDetailsService(clientDetailsService); + BaseClientDetails client = new BaseClientDetails("client", null, "read", "authorization_code", null); + client.setAutoApproveScopes(new HashSet(Arrays.asList("read"))); + clientDetailsService.setClientDetailsStore(Collections.singletonMap("client", client)); + AuthorizationRequest authorizationRequest = new AuthorizationRequest("client", Arrays.asList("read")); + AuthorizationRequest result = handler.checkForPreApproval(authorizationRequest, userAuthentication); + + Collection approvals = store.getApprovals(userAuthentication.getName(), "client"); + assertEquals(1, approvals.size()); + + Approval approval = approvals.iterator().next(); + assertEquals("read", approval.getScope()); + } + + @Test + public void testAutoapprovedAllScopes() { + handler.setClientDetailsService(clientDetailsService); + BaseClientDetails client = new BaseClientDetails("client", null, "read", "authorization_code", null); + client.setAutoApproveScopes(new HashSet(Arrays.asList("true"))); + clientDetailsService.setClientDetailsStore(Collections.singletonMap("client", client)); + AuthorizationRequest authorizationRequest = new AuthorizationRequest("client", Arrays.asList("read")); + AuthorizationRequest result = handler.checkForPreApproval(authorizationRequest, userAuthentication); + assertTrue(result.isApproved()); + } + + @Test + public void testExpiredPreapprovedScopes() { + store.addApprovals(Arrays.asList(new Approval("user", "client", "read", new Date( + System.currentTimeMillis() - 10000), Approval.ApprovalStatus.APPROVED))); + AuthorizationRequest authorizationRequest = new AuthorizationRequest("client", Arrays.asList("read")); + AuthorizationRequest result = handler.checkForPreApproval(authorizationRequest, userAuthentication); + assertFalse(result.isApproved()); + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/TestDefaultUserApprovalHandler.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/DefaultUserApprovalHandlerTests.java similarity index 69% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/TestDefaultUserApprovalHandler.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/DefaultUserApprovalHandlerTests.java index 24e93120e..f1cb3c2bd 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/TestDefaultUserApprovalHandler.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/DefaultUserApprovalHandlerTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -12,30 +12,32 @@ */ package org.springframework.security.oauth2.provider.approval; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import java.util.HashMap; import org.junit.Test; import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; +import org.springframework.security.oauth2.provider.AuthorizationRequest; /** * @author Dave Syer * */ -public class TestDefaultUserApprovalHandler { +public class DefaultUserApprovalHandlerTests { private DefaultUserApprovalHandler handler = new DefaultUserApprovalHandler(); @Test public void testBasicApproval() { - DefaultAuthorizationRequest request =new DefaultAuthorizationRequest(new HashMap()); - request.setApproved(true); // This isn't enough to be explicitly approved - assertFalse(handler.isApproved(request, new TestAuthentication("marissa", true))); + AuthorizationRequest request = new AuthorizationRequest(new HashMap(), null, null, null, null, null, false, null, null, null); + request.setApproved(true); // This is enough to be explicitly approved + assertTrue(handler.isApproved(request, new TestAuthentication("marissa", true))); } protected static class TestAuthentication extends AbstractAuthenticationToken { + + private static final long serialVersionUID = 1L; private String principal; public TestAuthentication(String name, boolean authenticated) { diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/InMemoryApprovalStoreTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/InMemoryApprovalStoreTests.java new file mode 100644 index 000000000..730dc47fd --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/InMemoryApprovalStoreTests.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.approval; + +/** + * @author Dave Syer + * + */ +public class InMemoryApprovalStoreTests extends AbstractTestApprovalStore { + + private ApprovalStore store = new InMemoryApprovalStore(); + + @Override + protected ApprovalStore getApprovalStore() { + return store ; + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/JdbcApprovalStoreTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/JdbcApprovalStoreTests.java new file mode 100644 index 000000000..f202d9be1 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/JdbcApprovalStoreTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.approval; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Date; + +import org.junit.After; +import org.junit.Test; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.security.oauth2.provider.approval.Approval.ApprovalStatus; + +/** + * @author Dave Syer + * + */ +public class JdbcApprovalStoreTests extends AbstractTestApprovalStore { + + private JdbcApprovalStore store; + + private EmbeddedDatabase db; + + @After + public void tearDown() throws Exception { + db.shutdown(); + } + + @Override + protected ApprovalStore getApprovalStore() { + db = new EmbeddedDatabaseBuilder().addDefaultScripts().build(); + store = new JdbcApprovalStore(db); + return store; + } + + @Test + public void testRevokeByExpiry() { + store.setHandleRevocationsAsExpiry(true); + Approval approval1 = new Approval("user", "client", "read", 10000, + ApprovalStatus.APPROVED); + Approval approval2 = new Approval("user", "client", "write", 10000, + ApprovalStatus.APPROVED); + assertTrue(store.addApprovals(Arrays. asList(approval1, + approval2))); + store.revokeApprovals(Arrays.asList(approval1)); + assertEquals(2, store.getApprovals("user", "client").size()); + assertEquals( + new Integer(1), + new JdbcTemplate(db) + .queryForObject( + "SELECT COUNT(*) from oauth_approvals where userId='user' AND expiresAt < ?", + Integer.class, + new Date(System.currentTimeMillis() + 1000))); + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/TestTokenServicesUserApprovalHandler.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/TestTokenServicesUserApprovalHandler.java deleted file mode 100644 index a809e2fb6..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/TestTokenServicesUserApprovalHandler.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2006-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package org.springframework.security.oauth2.provider.approval; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.util.Collections; -import java.util.HashMap; - -import org.junit.Test; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.DefaultTokenServices; -import org.springframework.security.oauth2.provider.token.InMemoryTokenStore; - -/** - * @author Dave Syer - * - */ -public class TestTokenServicesUserApprovalHandler { - - private TokenServicesUserApprovalHandler handler = new TokenServicesUserApprovalHandler(); - - private DefaultTokenServices tokenServices = new DefaultTokenServices(); - - { - tokenServices.setTokenStore(new InMemoryTokenStore()); - handler.setTokenServices(tokenServices); - } - - @Test(expected = IllegalStateException.class) - public void testMandatoryProperties() throws Exception { - handler = new TokenServicesUserApprovalHandler(); - handler.afterPropertiesSet(); - } - - @Test - public void testBasicApproval() { - DefaultAuthorizationRequest request = new DefaultAuthorizationRequest(new HashMap()); - request.setApproved(true); // This isn't enough to be explicitly approved - assertFalse(handler.isApproved(request , new TestAuthentication("marissa", true))); - } - - @Test - public void testMemorizedApproval() { - DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest(Collections.singletonMap( - "client_id", "foo")); - authorizationRequest.setApproved(false); - TestAuthentication userAuthentication = new TestAuthentication("marissa", true); - tokenServices.createAccessToken(new OAuth2Authentication(authorizationRequest, userAuthentication)); - assertTrue(handler.isApproved(authorizationRequest, userAuthentication)); - } - - protected static class TestAuthentication extends AbstractAuthenticationToken { - private String principal; - - public TestAuthentication(String name, boolean authenticated) { - super(null); - setAuthenticated(authenticated); - this.principal = name; - } - - public Object getCredentials() { - return null; - } - - public Object getPrincipal() { - return this.principal; - } - } - -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/TokenApprovalStoreTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/TokenApprovalStoreTests.java new file mode 100644 index 000000000..81dce821a --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/TokenApprovalStoreTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.approval; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; + +/** + * @author Dave Syer + * + */ +public class TokenApprovalStoreTests extends AbstractTestApprovalStore { + + private TokenApprovalStore store = new TokenApprovalStore(); + private InMemoryTokenStore tokenStore = new InMemoryTokenStore(); + + @Override + protected ApprovalStore getApprovalStore() { + store.setTokenStore(tokenStore); + return store ; + } + + @Override + protected boolean addApprovals(Collection approvals) { + + Map>> clientIds = new HashMap>>(); + for (Approval approval : approvals) { + String clientId = approval.getClientId(); + if (!clientIds.containsKey(clientId)) { + clientIds.put(clientId, new HashMap>()); + } + String userId = approval.getUserId(); + Map> users = clientIds.get(clientId); + if (!users.containsKey(userId)) { + users.put(userId, new HashSet()); + } + Set scopes = users.get(userId); + scopes.add(approval.getScope()); + } + + for (String clientId : clientIds.keySet()) { + Map> users = clientIds.get(clientId); + for (String userId : users.keySet()) { + Authentication user = new UsernamePasswordAuthenticationToken(userId, "N/A", AuthorityUtils.commaSeparatedStringToAuthorityList("USER")); + AuthorizationRequest authorizationRequest = new AuthorizationRequest(); + authorizationRequest.setClientId(clientId); + Set scopes = users.get(userId); + authorizationRequest.setScope(scopes); + OAuth2Request request = authorizationRequest.createOAuth2Request(); + OAuth2Authentication authentication = new OAuth2Authentication(request, user); + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString()); + token.setScope(scopes); + tokenStore.storeAccessToken(token, authentication); + } + } + return super.addApprovals(approvals); + } + + protected int getExpectedNumberOfApprovalsAfterRevoke() { + return 0; + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/TokenStoreUserApprovalHandlerTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/TokenStoreUserApprovalHandlerTests.java new file mode 100644 index 000000000..c52b0d9f6 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/approval/TokenStoreUserApprovalHandlerTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2006-2011 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.provider.approval; + +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; + +import org.junit.Test; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; + +/** + * @author Dave Syer + * + */ +public class TokenStoreUserApprovalHandlerTests { + + private TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler(); + + private DefaultTokenServices tokenServices = new DefaultTokenServices(); + + private DefaultOAuth2RequestFactory requestFactory = new DefaultOAuth2RequestFactory(null); + + { + InMemoryTokenStore tokenStore = new InMemoryTokenStore(); + tokenServices.setTokenStore(tokenStore); + handler.setTokenStore(tokenStore); + handler.setRequestFactory(requestFactory); + } + + @Test(expected = IllegalStateException.class) + public void testMandatoryProperties() throws Exception { + handler = new TokenStoreUserApprovalHandler(); + handler.afterPropertiesSet(); + } + + @Test + public void testBasicApproval() { + HashMap parameters = new HashMap(); + parameters.put(OAuth2Utils.USER_OAUTH_APPROVAL, "true"); + AuthorizationRequest request = new AuthorizationRequest(parameters, null, null, null, null, null, false, null, null, null); + request.setApproved(true); // This is enough to be explicitly approved + assertTrue(handler.isApproved(request , new TestAuthentication("marissa", true))); + } + + @Test + public void testMemorizedApproval() { + HashMap parameters = new HashMap(); + parameters.put(OAuth2Utils.USER_OAUTH_APPROVAL, "false"); + parameters.put("client_id", "foo"); + AuthorizationRequest authorizationRequest = new AuthorizationRequest(parameters, null, "foo", null, null, null, false, null, null, null); + authorizationRequest.setApproved(false); + TestAuthentication userAuthentication = new TestAuthentication("marissa", true); + OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(authorizationRequest); + + tokenServices.createAccessToken(new OAuth2Authentication(storedOAuth2Request, userAuthentication)); + authorizationRequest = handler.checkForPreApproval(authorizationRequest, userAuthentication); + assertTrue(handler.isApproved(authorizationRequest, userAuthentication)); + } + + protected static class TestAuthentication extends AbstractAuthenticationToken { + + private static final long serialVersionUID = 1L; + private String principal; + + public TestAuthentication(String name, boolean authenticated) { + super(null); + setAuthenticated(authenticated); + this.principal = name; + } + + public Object getCredentials() { + return null; + } + + public Object getPrincipal() { + return this.principal; + } + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationDetailsTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationDetailsTests.java new file mode 100644 index 000000000..3674c6e45 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationDetailsTests.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013-2014 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.provider.authentication; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.util.SerializationUtils; + +/** + * @author Dave Syer + * + */ +public class OAuth2AuthenticationDetailsTests { + + @Test + public void testSerializationWithDetails() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, "FOO"); + request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, "bearer"); + OAuth2AuthenticationDetails holder = new OAuth2AuthenticationDetails(request); + OAuth2AuthenticationDetails other = (OAuth2AuthenticationDetails) SerializationUtils.deserialize(SerializationUtils + .serialize(holder)); + assertEquals(holder, other); + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationManagerTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationManagerTests.java new file mode 100644 index 000000000..9ba7107cc --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationManagerTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2006-2011 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.provider.authentication; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.RequestTokenFactory; +import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; + +/** + * @author Dave Syer + * + */ +public class OAuth2AuthenticationManagerTests { + + private OAuth2AuthenticationManager manager = new OAuth2AuthenticationManager(); + + private ResourceServerTokenServices tokenServices = Mockito.mock(ResourceServerTokenServices.class); + + private Authentication userAuthentication = new UsernamePasswordAuthenticationToken("marissa", "koala"); + + private OAuth2Authentication authentication = new OAuth2Authentication( + RequestTokenFactory.createOAuth2Request("foo", false), userAuthentication); + + { + manager.setTokenServices(tokenServices); + } + + @Test + public void testDetailsAdded() throws Exception { + Mockito.when(tokenServices.loadAuthentication("FOO")).thenReturn(authentication); + PreAuthenticatedAuthenticationToken request = new PreAuthenticatedAuthenticationToken("FOO", ""); + request.setDetails("BAR"); + Authentication result = manager.authenticate(request); + assertEquals(authentication, result); + assertEquals("BAR", result.getDetails()); + } + + @Test + public void testDetailsEnhanced() throws Exception { + authentication.setDetails("DETAILS"); + Mockito.when(tokenServices.loadAuthentication("FOO")).thenReturn(authentication); + PreAuthenticatedAuthenticationToken request = new PreAuthenticatedAuthenticationToken("FOO", ""); + MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + servletRequest.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, "BAR"); + OAuth2AuthenticationDetails details = new OAuth2AuthenticationDetails(servletRequest); + request.setDetails(details); + Authentication result = manager.authenticate(request); + assertEquals(authentication, result); + assertEquals("BAR", ((OAuth2AuthenticationDetails) result.getDetails()).getTokenValue()); + assertEquals("DETAILS", ((OAuth2AuthenticationDetails) result.getDetails()).getDecodedDetails()); + } + + @Test + public void testDetailsEnhancedOnce() throws Exception { + authentication.setDetails("DETAILS"); + Mockito.when(tokenServices.loadAuthentication("FOO")).thenReturn(authentication); + PreAuthenticatedAuthenticationToken request = new PreAuthenticatedAuthenticationToken("FOO", ""); + MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + servletRequest.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, "BAR"); + OAuth2AuthenticationDetails details = new OAuth2AuthenticationDetails(servletRequest); + request.setDetails(details); + Authentication result = manager.authenticate(request); + // Authenticate the same request again to simulate what happens if the app is caching the result from + // tokenServices.loadAuthentication(): + result = manager.authenticate(request); + assertEquals(authentication, result); + assertEquals("BAR", ((OAuth2AuthenticationDetails) result.getDetails()).getTokenValue()); + assertEquals("DETAILS", ((OAuth2AuthenticationDetails) result.getDetails()).getDecodedDetails()); + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationProcessingFilterTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationProcessingFilterTests.java new file mode 100644 index 000000000..e77bceea7 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/OAuth2AuthenticationProcessingFilterTests.java @@ -0,0 +1,153 @@ +/* + * Copyright 2006-2011 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.provider.authentication; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import javax.servlet.FilterChain; + +import org.junit.After; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.authentication.AuthenticationEventPublisher; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.RequestTokenFactory; + +/** + * @author Dave Syer + * + */ +public class OAuth2AuthenticationProcessingFilterTests { + + private OAuth2AuthenticationProcessingFilter filter = new OAuth2AuthenticationProcessingFilter(); + + private MockHttpServletRequest request = new MockHttpServletRequest(); + + private MockHttpServletResponse response = new MockHttpServletResponse(); + + private Authentication userAuthentication = new UsernamePasswordAuthenticationToken("marissa", "koala"); + + private OAuth2Authentication authentication = new OAuth2Authentication(RequestTokenFactory.createOAuth2Request( + null, "foo", null, false, null, null, null, null, null), userAuthentication); + + private FilterChain chain = Mockito.mock(FilterChain.class); + + { + filter.setAuthenticationManager(new AuthenticationManager() { + + public Authentication authenticate(Authentication request) throws AuthenticationException { + if ("BAD".equals(request.getPrincipal())) { + throw new InvalidTokenException("Invalid token"); + } + authentication.setDetails(request.getDetails()); + return authentication; + } + }); + } + + @After + public void clear() { + SecurityContextHolder.clearContext(); + } + + @Test + public void testDetailsAdded() throws Exception { + request.addHeader("Authorization", "Bearer FOO"); + filter.doFilter(request, null, chain); + assertNotNull(request.getAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE)); + assertEquals("Bearer", request.getAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE)); + Authentication result = SecurityContextHolder.getContext().getAuthentication(); + assertEquals(authentication, result); + assertNotNull(result.getDetails()); + } + + @Test + public void testDetailsAddedWithForm() throws Exception { + request.addParameter("access_token", "FOO"); + filter.doFilter(request, null, chain); + assertNotNull(request.getAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE)); + assertEquals(OAuth2AccessToken.BEARER_TYPE, request.getAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE)); + Authentication result = SecurityContextHolder.getContext().getAuthentication(); + assertEquals(authentication, result); + assertNotNull(result.getDetails()); + } + + @Test + public void testStateless() throws Exception { + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken("FOO", "foo", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"))); + filter.doFilter(request, null, chain); + assertNull(SecurityContextHolder.getContext().getAuthentication()); + } + + @Test + public void testStatelessPreservesAnonymous() throws Exception { + SecurityContextHolder.getContext().setAuthentication( + new AnonymousAuthenticationToken("FOO", "foo", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"))); + filter.doFilter(request, null, chain); + assertNotNull(SecurityContextHolder.getContext().getAuthentication()); + } + + @Test + public void testStateful() throws Exception { + filter.setStateless(false); + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken("FOO", "foo", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"))); + filter.doFilter(request, null, chain); + assertNotNull(SecurityContextHolder.getContext().getAuthentication()); + } + + @Test + public void testNoEventsPublishedWithNoToken() throws Exception { + AuthenticationEventPublisher eventPublisher = Mockito.mock(AuthenticationEventPublisher.class); + filter.setAuthenticationEventPublisher(eventPublisher); + filter.doFilter(request, null, chain); + Mockito.verify(eventPublisher, Mockito.never()).publishAuthenticationFailure(Mockito.any(AuthenticationException.class), Mockito.any(Authentication.class)); + Mockito.verify(eventPublisher, Mockito.never()).publishAuthenticationSuccess(Mockito.any(Authentication.class)); + } + + @Test + public void testSuccessEventsPublishedWithToken() throws Exception { + request.addHeader("Authorization", "Bearer FOO"); + AuthenticationEventPublisher eventPublisher = Mockito.mock(AuthenticationEventPublisher.class); + filter.setAuthenticationEventPublisher(eventPublisher); + filter.doFilter(request, null, chain); + Mockito.verify(eventPublisher, Mockito.never()).publishAuthenticationFailure(Mockito.any(AuthenticationException.class), Mockito.any(Authentication.class)); + Mockito.verify(eventPublisher).publishAuthenticationSuccess(Mockito.any(Authentication.class)); + } + + @Test + public void testFailureEventsPublishedWithBadToken() throws Exception { + request.addHeader("Authorization", "Bearer BAD"); + AuthenticationEventPublisher eventPublisher = Mockito.mock(AuthenticationEventPublisher.class); + filter.setAuthenticationEventPublisher(eventPublisher); + filter.doFilter(request, response, chain); + Mockito.verify(eventPublisher).publishAuthenticationFailure(Mockito.any(AuthenticationException.class), Mockito.any(Authentication.class)); + Mockito.verify(eventPublisher, Mockito.never()).publishAuthenticationSuccess(Mockito.any(Authentication.class)); + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/TestOAuth2AuthenticationManager.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/TestOAuth2AuthenticationManager.java deleted file mode 100644 index 643f8de21..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/TestOAuth2AuthenticationManager.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2006-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - - -package org.springframework.security.oauth2.provider.authentication; - -import static org.junit.Assert.assertEquals; - -import java.util.Collections; - -import org.junit.Test; -import org.mockito.Mockito; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; - -/** - * @author Dave Syer - * - */ -public class TestOAuth2AuthenticationManager { - - private OAuth2AuthenticationManager manager = new OAuth2AuthenticationManager(); - - private ResourceServerTokenServices tokenServices = Mockito.mock(ResourceServerTokenServices.class); - - private Authentication userAuthentication = new UsernamePasswordAuthenticationToken("marissa", "koala"); - - private OAuth2Authentication authentication = new OAuth2Authentication(new DefaultAuthorizationRequest( - Collections. emptyMap()), userAuthentication); - - { - manager.setTokenServices(tokenServices); - } - - @Test - public void testDetailsAdded() throws Exception { - Mockito.when(tokenServices.loadAuthentication("FOO")).thenReturn(authentication); - PreAuthenticatedAuthenticationToken request = new PreAuthenticatedAuthenticationToken("FOO", ""); - request.setDetails("BAR"); - Authentication result = manager.authenticate(request); - assertEquals(authentication, result); - assertEquals("BAR", result.getDetails()); - } - -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/TestOAuth2AuthenticationProcessingFilter.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/TestOAuth2AuthenticationProcessingFilter.java deleted file mode 100644 index 825cfd7a0..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/authentication/TestOAuth2AuthenticationProcessingFilter.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2006-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - - -package org.springframework.security.oauth2.provider.authentication; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import java.util.Collections; - -import javax.servlet.FilterChain; - -import org.junit.After; -import org.junit.Test; -import org.mockito.Mockito; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; -import org.springframework.security.oauth2.provider.OAuth2Authentication; - -/** - * @author Dave Syer - * - */ -public class TestOAuth2AuthenticationProcessingFilter { - - private OAuth2AuthenticationProcessingFilter filter = new OAuth2AuthenticationProcessingFilter(); - - private MockHttpServletRequest request = new MockHttpServletRequest(); - - private Authentication userAuthentication = new UsernamePasswordAuthenticationToken("marissa", "koala"); - - private OAuth2Authentication authentication = new OAuth2Authentication(new DefaultAuthorizationRequest( - Collections. emptyMap()), userAuthentication); - - private FilterChain chain = Mockito.mock(FilterChain.class); - - { - filter.setAuthenticationManager(new AuthenticationManager() { - - public Authentication authenticate(Authentication request) throws AuthenticationException { - authentication.setDetails(request.getDetails()); - return authentication; - } - }); - } - - @After - public void clear() { - SecurityContextHolder.clearContext(); - } - - @Test - public void testDetailsAdded() throws Exception { - request.addHeader("Authorization", "Bearer FOO"); - filter.doFilter(request, null, chain ); - assertNotNull(request.getAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE)); - Authentication result = SecurityContextHolder.getContext().getAuthentication(); - assertEquals(authentication, result); - assertNotNull(result.getDetails()); - } - -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestBaseClientDetails.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/client/BaseClientDetailsTests.java similarity index 66% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestBaseClientDetails.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/client/BaseClientDetailsTests.java index 6514adbad..9ab738793 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestBaseClientDetails.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/client/BaseClientDetailsTests.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,21 +14,25 @@ * limitations under the License. */ -package org.springframework.security.oauth2.provider; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +package org.springframework.security.oauth2.provider.client; import java.util.Collections; +import java.util.TreeSet; -import org.codehaus.jackson.map.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; +import org.springframework.util.StringUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + /** * @author Dave Syer * */ -public class TestBaseClientDetails { +public class BaseClientDetailsTests { /** * test default constructor @@ -49,11 +53,41 @@ public void testBaseClientDetailsDefaultConstructor() { public void testBaseClientDetailsConvenienceConstructor() { BaseClientDetails details = new BaseClientDetails("foo", "", "foo,bar", "authorization_code", "ROLE_USER"); assertEquals("[]", details.getResourceIds().toString()); - assertEquals("[bar, foo]", details.getScope().toString()); + assertEquals("[bar, foo]", new TreeSet(details.getScope()).toString()); assertEquals("[authorization_code]", details.getAuthorizedGrantTypes().toString()); assertEquals("[ROLE_USER]", details.getAuthorities().toString()); } + /** + * test explicit autoapprove + */ + @Test + public void testBaseClientDetailsAutoApprove() { + BaseClientDetails details = new BaseClientDetails("foo", "", "foo,bar", "authorization_code", "ROLE_USER"); + details.setAutoApproveScopes(StringUtils.commaDelimitedListToSet("read,write")); + assertTrue(details.isAutoApprove("read")); + } + + @Test + public void testBaseClientDetailsImplicitAutoApprove() { + BaseClientDetails details = new BaseClientDetails("foo", "", "foo,bar", "authorization_code", "ROLE_USER"); + details.setAutoApproveScopes(StringUtils.commaDelimitedListToSet("true")); + assertTrue(details.isAutoApprove("read")); + } + + @Test + public void testBaseClientDetailsNoAutoApprove() { + BaseClientDetails details = new BaseClientDetails("foo", "", "foo,bar", "authorization_code", "ROLE_USER"); + details.setAutoApproveScopes(StringUtils.commaDelimitedListToSet("none")); + assertFalse(details.isAutoApprove("read")); + } + + @Test + public void testBaseClientDetailsNullAutoApprove() { + BaseClientDetails details = new BaseClientDetails("foo", "", "foo,bar", "authorization_code", "ROLE_USER"); + assertFalse(details.isAutoApprove("read")); + } + @Test public void testJsonSerialize() throws Exception { BaseClientDetails details = new BaseClientDetails("foo", "", "foo,bar", "authorization_code", "ROLE_USER"); @@ -94,4 +128,16 @@ public void testJsonDeserializeWithArraysAsStrings() throws Exception { assertEquals(expected, details); } + /** + * test equality + */ + @Test + public void testEqualityOfValidity() { + BaseClientDetails details = new BaseClientDetails(); + details.setAccessTokenValiditySeconds(100); + BaseClientDetails other = new BaseClientDetails(); + other.setAccessTokenValiditySeconds(100); + assertEquals(details, other); + } + } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/client/ClientCredentialsTokenEndpointFilterTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/client/ClientCredentialsTokenEndpointFilterTests.java new file mode 100644 index 000000000..cc3054244 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/client/ClientCredentialsTokenEndpointFilterTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * 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.provider.client; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; + +/** + * @author Dave Syer + * + */ +public class ClientCredentialsTokenEndpointFilterTests { + + private ClientCredentialsTokenEndpointFilter filter = new ClientCredentialsTokenEndpointFilter(); + private AuthenticationManager authenticationManager = Mockito + .mock(AuthenticationManager.class); + + @Test(expected=IllegalArgumentException.class) + public void testAuthenticationManagerNeeded() { + new ClientCredentialsTokenEndpointFilter().afterPropertiesSet(); + } + + @Test(expected = BadCredentialsException.class) + public void testFailedAuthentication() throws Exception { + filter.setAuthenticationManager(authenticationManager); + filter.afterPropertiesSet(); + filter.attemptAuthentication(new MockHttpServletRequest(), + new MockHttpServletResponse()); + } + + @Test + public void testAuthentication() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("client_id", "foo"); + filter.setAuthenticationManager(authenticationManager); + filter.afterPropertiesSet(); + Authentication authentication = new UsernamePasswordAuthenticationToken( + "foo", "", + AuthorityUtils.commaSeparatedStringToAuthorityList("CLIENT")); + Mockito.when( + authenticationManager.authenticate(Mockito + .any(Authentication.class))).thenReturn(authentication); + assertEquals(authentication, filter.attemptAuthentication(request, + new MockHttpServletResponse())); + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/client/ClientDetailsUserDetailsServiceTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/client/ClientDetailsUserDetailsServiceTests.java new file mode 100644 index 000000000..aa4878bce --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/client/ClientDetailsUserDetailsServiceTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * 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.provider.client; + +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.ClientRegistrationException; +import org.springframework.security.oauth2.provider.NoSuchClientException; +import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Ruslan Forostianov + */ +public class ClientDetailsUserDetailsServiceTests { + + @SuppressWarnings("unchecked") + @Test(expected = UsernameNotFoundException.class) + public void shouldThrowUsernameNotFoundExceptionWhenNoSuchClient() throws Exception { + + Map map = new HashMap(); + map.put(UserAuthenticationConverter.USERNAME, "test_user"); + + ClientDetailsService clientDetailsService = Mockito.mock(ClientDetailsService.class); + Mockito.when(clientDetailsService.loadClientByClientId("test_user")).thenThrow(NoSuchClientException.class); + ClientDetailsUserDetailsService testee = new ClientDetailsUserDetailsService(clientDetailsService); + + testee.loadUserByUsername("test_user"); + } + + @SuppressWarnings("unchecked") + @Test(expected = ClientRegistrationException.class) + public void shouldConductOriginalException() throws Exception { + + Map map = new HashMap(); + map.put(UserAuthenticationConverter.USERNAME, "test_user"); + + ClientDetailsService clientDetailsService = Mockito.mock(ClientDetailsService.class); + Mockito.when(clientDetailsService.loadClientByClientId("test_user")).thenThrow(ClientRegistrationException.class); + ClientDetailsUserDetailsService testee = new ClientDetailsUserDetailsService(clientDetailsService); + + testee.loadUserByUsername("test_user"); + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestJdbcClientDetailsService.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/client/JdbcClientDetailsServiceTests.java similarity index 70% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestJdbcClientDetailsService.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/client/JdbcClientDetailsServiceTests.java index 2ed49744f..762aefe0e 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/TestJdbcClientDetailsService.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/client/JdbcClientDetailsServiceTests.java @@ -1,4 +1,4 @@ -package org.springframework.security.oauth2.provider; +package org.springframework.security.oauth2.provider.client; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -19,8 +19,13 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.provider.ClientAlreadyExistsException; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.NoSuchClientException; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; -public class TestJdbcClientDetailsService { +public class JdbcClientDetailsServiceTests { private JdbcClientDetailsService service; private JdbcTemplate jdbcTemplate; @@ -29,13 +34,14 @@ public class TestJdbcClientDetailsService { private static final String SELECT_SQL = "select client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity from oauth_client_details where client_id=?"; - private static final String INSERT_SQL = "insert into oauth_client_details (client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity) values (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + private static final String INSERT_SQL = "insert into oauth_client_details (client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, autoapprove) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; private static final String CUSTOM_INSERT_SQL = "insert into ClientDetails (appId, appSecret, resourceIds, scope, grantTypes, redirectUrl, authorities) values (?, ?, ?, ?, ?, ?, ?)"; @Before public void setUp() throws Exception { - // creates a HSQL in-memory db populated from default scripts classpath:schema.sql and classpath:data.sql + // creates a HSQL in-memory db populated from default scripts + // classpath:schema.sql and classpath:data.sql db = new EmbeddedDatabaseBuilder().addDefaultScripts().build(); jdbcTemplate = new JdbcTemplate(db); service = new JdbcClientDetailsService(db); @@ -53,9 +59,11 @@ public void testLoadingClientForNonExistingClientId() { @Test public void testLoadingClientIdWithNoDetails() { - jdbcTemplate.update(INSERT_SQL, "clientIdWithNoDetails", null, null, null, null, null, null, null, null); + jdbcTemplate.update(INSERT_SQL, "clientIdWithNoDetails", null, null, + null, null, null, null, null, null, null); - ClientDetails clientDetails = service.loadClientByClientId("clientIdWithNoDetails"); + ClientDetails clientDetails = service + .loadClientByClientId("clientIdWithNoDetails"); assertEquals("clientIdWithNoDetails", clientDetails.getClientId()); assertFalse(clientDetails.isSecretRequired()); @@ -71,21 +79,28 @@ public void testLoadingClientIdWithNoDetails() { @Test public void testLoadingClientIdWithAdditionalInformation() { - jdbcTemplate.update(INSERT_SQL, "clientIdWithAddInfo", null, null, null, null, null, null, null, null); - jdbcTemplate.update("update oauth_client_details set additional_information=? where client_id=?", "{\"foo\":\"bar\"}", "clientIdWithAddInfo"); + jdbcTemplate.update(INSERT_SQL, "clientIdWithAddInfo", null, null, + null, null, null, null, null, null, null); + jdbcTemplate + .update("update oauth_client_details set additional_information=? where client_id=?", + "{\"foo\":\"bar\"}", "clientIdWithAddInfo"); - ClientDetails clientDetails = service.loadClientByClientId("clientIdWithAddInfo"); + ClientDetails clientDetails = service + .loadClientByClientId("clientIdWithAddInfo"); assertEquals("clientIdWithAddInfo", clientDetails.getClientId()); - assertEquals(Collections.singletonMap("foo", "bar"), clientDetails.getAdditionalInformation()); + assertEquals(Collections.singletonMap("foo", "bar"), + clientDetails.getAdditionalInformation()); } @Test public void testLoadingClientIdWithSingleDetails() { - jdbcTemplate.update(INSERT_SQL, "clientIdWithSingleDetails", "mySecret", "myResource", "myScope", - "myAuthorizedGrantType", "myRedirectUri", "myAuthority", 100, 200); + jdbcTemplate.update(INSERT_SQL, "clientIdWithSingleDetails", + "mySecret", "myResource", "myScope", "myAuthorizedGrantType", + "myRedirectUri", "myAuthority", 100, 200, "true"); - ClientDetails clientDetails = service.loadClientByClientId("clientIdWithSingleDetails"); + ClientDetails clientDetails = service + .loadClientByClientId("clientIdWithSingleDetails"); assertEquals("clientIdWithSingleDetails", clientDetails.getClientId()); assertTrue(clientDetails.isSecretRequired()); @@ -94,27 +109,36 @@ public void testLoadingClientIdWithSingleDetails() { assertEquals(1, clientDetails.getScope().size()); assertEquals("myScope", clientDetails.getScope().iterator().next()); assertEquals(1, clientDetails.getResourceIds().size()); - assertEquals("myResource", clientDetails.getResourceIds().iterator().next()); + assertEquals("myResource", clientDetails.getResourceIds().iterator() + .next()); assertEquals(1, clientDetails.getAuthorizedGrantTypes().size()); - assertEquals("myAuthorizedGrantType", clientDetails.getAuthorizedGrantTypes().iterator().next()); - assertEquals("myRedirectUri", clientDetails.getRegisteredRedirectUri().iterator().next()); + assertEquals("myAuthorizedGrantType", clientDetails + .getAuthorizedGrantTypes().iterator().next()); + assertEquals("myRedirectUri", clientDetails.getRegisteredRedirectUri() + .iterator().next()); assertEquals(1, clientDetails.getAuthorities().size()); - assertEquals("myAuthority", clientDetails.getAuthorities().iterator().next().getAuthority()); - assertEquals(new Integer(100), clientDetails.getAccessTokenValiditySeconds()); - assertEquals(new Integer(200), clientDetails.getRefreshTokenValiditySeconds()); + assertEquals("myAuthority", clientDetails.getAuthorities().iterator() + .next().getAuthority()); + assertEquals(new Integer(100), + clientDetails.getAccessTokenValiditySeconds()); + assertEquals(new Integer(200), + clientDetails.getRefreshTokenValiditySeconds()); } @Test public void testLoadingClientIdWithSingleDetailsInCustomTable() { - jdbcTemplate.update(CUSTOM_INSERT_SQL, "clientIdWithSingleDetails", "mySecret", "myResource", "myScope", - "myAuthorizedGrantType", "myRedirectUri", "myAuthority"); + jdbcTemplate.update(CUSTOM_INSERT_SQL, "clientIdWithSingleDetails", + "mySecret", "myResource", "myScope", "myAuthorizedGrantType", + "myRedirectUri", "myAuthority"); - JdbcClientDetailsService customService = new JdbcClientDetailsService(db); + JdbcClientDetailsService customService = new JdbcClientDetailsService( + db); customService .setSelectClientDetailsSql("select appId, appSecret, resourceIds, scope, " - + "grantTypes, redirectUrl, authorities, access_token_validity, refresh_token_validity, additionalInformation from ClientDetails where appId = ?"); + + "grantTypes, redirectUrl, authorities, access_token_validity, refresh_token_validity, additionalInformation, autoApproveScopes from ClientDetails where appId = ?"); - ClientDetails clientDetails = customService.loadClientByClientId("clientIdWithSingleDetails"); + ClientDetails clientDetails = customService + .loadClientByClientId("clientIdWithSingleDetails"); assertEquals("clientIdWithSingleDetails", clientDetails.getClientId()); assertTrue(clientDetails.isSecretRequired()); @@ -123,28 +147,36 @@ public void testLoadingClientIdWithSingleDetailsInCustomTable() { assertEquals(1, clientDetails.getScope().size()); assertEquals("myScope", clientDetails.getScope().iterator().next()); assertEquals(1, clientDetails.getResourceIds().size()); - assertEquals("myResource", clientDetails.getResourceIds().iterator().next()); + assertEquals("myResource", clientDetails.getResourceIds().iterator() + .next()); assertEquals(1, clientDetails.getAuthorizedGrantTypes().size()); - assertEquals("myAuthorizedGrantType", clientDetails.getAuthorizedGrantTypes().iterator().next()); - assertEquals("myRedirectUri", clientDetails.getRegisteredRedirectUri().iterator().next()); + assertEquals("myAuthorizedGrantType", clientDetails + .getAuthorizedGrantTypes().iterator().next()); + assertEquals("myRedirectUri", clientDetails.getRegisteredRedirectUri() + .iterator().next()); assertEquals(1, clientDetails.getAuthorities().size()); - assertEquals("myAuthority", clientDetails.getAuthorities().iterator().next().getAuthority()); + assertEquals("myAuthority", clientDetails.getAuthorities().iterator() + .next().getAuthority()); } @Test public void testLoadingClientIdWithMultipleDetails() { - jdbcTemplate.update(INSERT_SQL, "clientIdWithMultipleDetails", "mySecret", "myResource1,myResource2", - "myScope1,myScope2", "myAuthorizedGrantType1,myAuthorizedGrantType2", "myRedirectUri1,myRedirectUri2", - "myAuthority1,myAuthority2", 100, 200); + jdbcTemplate.update(INSERT_SQL, "clientIdWithMultipleDetails", + "mySecret", "myResource1,myResource2", "myScope1,myScope2", + "myAuthorizedGrantType1,myAuthorizedGrantType2", + "myRedirectUri1,myRedirectUri2", "myAuthority1,myAuthority2", + 100, 200, "read,write"); - ClientDetails clientDetails = service.loadClientByClientId("clientIdWithMultipleDetails"); + ClientDetails clientDetails = service + .loadClientByClientId("clientIdWithMultipleDetails"); assertEquals("clientIdWithMultipleDetails", clientDetails.getClientId()); assertTrue(clientDetails.isSecretRequired()); assertEquals("mySecret", clientDetails.getClientSecret()); assertTrue(clientDetails.isScoped()); assertEquals(2, clientDetails.getResourceIds().size()); - Iterator resourceIds = clientDetails.getResourceIds().iterator(); + Iterator resourceIds = clientDetails.getResourceIds() + .iterator(); assertEquals("myResource1", resourceIds.next()); assertEquals("myResource2", resourceIds.next()); assertEquals(2, clientDetails.getScope().size()); @@ -152,19 +184,25 @@ public void testLoadingClientIdWithMultipleDetails() { assertEquals("myScope1", scope.next()); assertEquals("myScope2", scope.next()); assertEquals(2, clientDetails.getAuthorizedGrantTypes().size()); - Iterator grantTypes = clientDetails.getAuthorizedGrantTypes().iterator(); + Iterator grantTypes = clientDetails.getAuthorizedGrantTypes() + .iterator(); assertEquals("myAuthorizedGrantType1", grantTypes.next()); assertEquals("myAuthorizedGrantType2", grantTypes.next()); assertEquals(2, clientDetails.getRegisteredRedirectUri().size()); - Iterator redirectUris = clientDetails.getRegisteredRedirectUri().iterator(); + Iterator redirectUris = clientDetails + .getRegisteredRedirectUri().iterator(); assertEquals("myRedirectUri1", redirectUris.next()); assertEquals("myRedirectUri2", redirectUris.next()); assertEquals(2, clientDetails.getAuthorities().size()); - Iterator authorities = clientDetails.getAuthorities().iterator(); + Iterator authorities = clientDetails.getAuthorities() + .iterator(); assertEquals("myAuthority1", authorities.next().getAuthority()); assertEquals("myAuthority2", authorities.next().getAuthority()); - assertEquals(new Integer(100), clientDetails.getAccessTokenValiditySeconds()); - assertEquals(new Integer(200), clientDetails.getRefreshTokenValiditySeconds()); + assertEquals(new Integer(100), + clientDetails.getAccessTokenValiditySeconds()); + assertEquals(new Integer(200), + clientDetails.getRefreshTokenValiditySeconds()); + assertTrue(clientDetails.isAutoApprove("read")); } @Test @@ -175,7 +213,8 @@ public void testAddClientWithNoDetails() { service.addClientDetails(clientDetails); - Map map = jdbcTemplate.queryForMap(SELECT_SQL, "addedClientIdWithNoDetails"); + Map map = jdbcTemplate.queryForMap(SELECT_SQL, + "addedClientIdWithNoDetails"); assertEquals("addedClientIdWithNoDetails", map.get("client_id")); assertTrue(map.containsKey("client_secret")); @@ -200,7 +239,8 @@ public void testUpdateClientSecret() { service.setPasswordEncoder(new PasswordEncoder() { - public boolean matches(CharSequence rawPassword, String encodedPassword) { + public boolean matches(CharSequence rawPassword, + String encodedPassword) { return true; } @@ -211,7 +251,8 @@ public String encode(CharSequence rawPassword) { service.addClientDetails(clientDetails); service.updateClientSecret(clientDetails.getClientId(), "foo"); - Map map = jdbcTemplate.queryForMap(SELECT_SQL, "newClientIdWithNoDetails"); + Map map = jdbcTemplate.queryForMap(SELECT_SQL, + "newClientIdWithNoDetails"); assertEquals("newClientIdWithNoDetails", map.get("client_id")); assertTrue(map.containsKey("client_secret")); @@ -226,16 +267,20 @@ public void testUpdateClientRedirectURI() { service.addClientDetails(clientDetails); - String[] redirectURI = { "http://localhost:8080/", "http://localhost:9090/" }; - clientDetails.setRegisteredRedirectUri(new HashSet(Arrays.asList(redirectURI))); + String[] redirectURI = { "http://localhost:8080/", + "http://localhost:9090/" }; + clientDetails.setRegisteredRedirectUri(new HashSet(Arrays + .asList(redirectURI))); service.updateClientDetails(clientDetails); - Map map = jdbcTemplate.queryForMap(SELECT_SQL, "newClientIdWithNoDetails"); + Map map = jdbcTemplate.queryForMap(SELECT_SQL, + "newClientIdWithNoDetails"); assertEquals("newClientIdWithNoDetails", map.get("client_id")); assertTrue(map.containsKey("web_server_redirect_uri")); - assertEquals("http://localhost:8080,http://localhost:9090", map.get("web_server_redirect_uri")); + assertEquals("http://localhost:8080,http://localhost:9090", + map.get("web_server_redirect_uri")); } @Test(expected = NoSuchClientException.class) @@ -256,8 +301,9 @@ public void testRemoveClient() { service.addClientDetails(clientDetails); service.removeClientDetails(clientDetails.getClientId()); - int count = jdbcTemplate.queryForInt("select count(*) from oauth_client_details where client_id=?", - "deletedClientIdWithNoDetails"); + int count = jdbcTemplate.queryForObject( + "select count(*) from oauth_client_details where client_id=?", + Integer.class, "deletedClientIdWithNoDetails"); assertEquals(0, count); } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestAuthorizationCodeServicesBase.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeServicesBaseTests.java similarity index 60% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestAuthorizationCodeServicesBase.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeServicesBaseTests.java index 61604ef2f..34b320772 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestAuthorizationCodeServicesBase.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeServicesBaseTests.java @@ -7,41 +7,42 @@ import org.junit.Test; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.RequestTokenFactory; -public abstract class TestAuthorizationCodeServicesBase { +public abstract class AuthorizationCodeServicesBaseTests { abstract AuthorizationCodeServices getAuthorizationCodeServices(); @Test public void testCreateAuthorizationCode() { - AuthorizationRequestHolder expectedAuthentication = new AuthorizationRequestHolder( - new DefaultAuthorizationRequest("id", null), new TestAuthentication( - "test2", false)); + OAuth2Request storedOAuth2Request = RequestTokenFactory.createOAuth2Request("id", false); + OAuth2Authentication expectedAuthentication = new OAuth2Authentication(storedOAuth2Request, + new TestAuthentication("test2", false)); String code = getAuthorizationCodeServices().createAuthorizationCode(expectedAuthentication); assertNotNull(code); - AuthorizationRequestHolder actualAuthentication = getAuthorizationCodeServices() - .consumeAuthorizationCode(code); + OAuth2Authentication actualAuthentication = getAuthorizationCodeServices().consumeAuthorizationCode(code); assertEquals(expectedAuthentication, actualAuthentication); } @Test public void testConsumeRemovesCode() { - AuthorizationRequestHolder expectedAuthentication = new AuthorizationRequestHolder( - new DefaultAuthorizationRequest("id", null), new TestAuthentication( - "test2", false)); + OAuth2Request storedOAuth2Request = RequestTokenFactory.createOAuth2Request("id", false); + OAuth2Authentication expectedAuthentication = new OAuth2Authentication(storedOAuth2Request, + new TestAuthentication("test2", false)); String code = getAuthorizationCodeServices().createAuthorizationCode(expectedAuthentication); assertNotNull(code); - AuthorizationRequestHolder actualAuthentication = getAuthorizationCodeServices() - .consumeAuthorizationCode(code); + OAuth2Authentication actualAuthentication = getAuthorizationCodeServices().consumeAuthorizationCode(code); assertEquals(expectedAuthentication, actualAuthentication); try { getAuthorizationCodeServices().consumeAuthorizationCode(code); fail("Should have thrown exception"); - } catch (InvalidGrantException e) { + } + catch (InvalidGrantException e) { // good we expected this } } @@ -51,13 +52,18 @@ public void testConsumeNonExistingCode() { try { getAuthorizationCodeServices().consumeAuthorizationCode("doesnt exist"); fail("Should have thrown exception"); - } catch (InvalidGrantException e) { + } + catch (InvalidGrantException e) { // good we expected this } } protected static class TestAuthentication extends AbstractAuthenticationToken { + + private static final long serialVersionUID = 1L; + private String principal; + public TestAuthentication(String name, boolean authenticated) { super(null); setAuthenticated(authenticated); @@ -67,7 +73,7 @@ public TestAuthentication(String name, boolean authenticated) { public Object getCredentials() { return null; } - + public Object getPrincipal() { return this.principal; } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestAuthorizationCodeTokenGranter.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeTokenGranterTests.java similarity index 53% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestAuthorizationCodeTokenGranter.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeTokenGranterTests.java index 48baf2956..4d9a0eece 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestAuthorizationCodeTokenGranter.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeTokenGranterTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -13,12 +13,6 @@ package org.springframework.security.oauth2.provider.code; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.springframework.security.oauth2.provider.AuthorizationRequest.REDIRECT_URI; - -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -31,20 +25,28 @@ import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException; -import org.springframework.security.oauth2.provider.AuthorizationRequest; -import org.springframework.security.oauth2.provider.BaseClientDetails; +import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.RequestTokenFactory; +import org.springframework.security.oauth2.provider.TokenRequest; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; -import org.springframework.security.oauth2.provider.token.InMemoryTokenStore; -import org.springframework.util.StringUtils; +import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @author Dave Syer * */ -public class TestAuthorizationCodeTokenGranter { +public class AuthorizationCodeTokenGranterTests { private DefaultTokenServices providerTokenServices = new DefaultTokenServices(); @@ -58,125 +60,144 @@ public ClientDetails loadClientByClientId(String clientId) throws OAuth2Exceptio }; private AuthorizationCodeServices authorizationCodeServices = new InMemoryAuthorizationCodeServices(); + + private OAuth2RequestFactory requestFactory = new DefaultOAuth2RequestFactory(clientDetailsService); private Map parameters = new HashMap(); - public TestAuthorizationCodeTokenGranter() { + public AuthorizationCodeTokenGranterTests() { providerTokenServices.setTokenStore(new InMemoryTokenStore()); } @Test public void testAuthorizationCodeGrant() { - DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest("foo", - Arrays.asList("scope")); - authorizationRequest.setApproved(true); + Authentication userAuthentication = new UsernamePasswordAuthenticationToken("marissa", "koala", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); - String code = authorizationCodeServices.createAuthorizationCode(new AuthorizationRequestHolder( - authorizationRequest, userAuthentication)); - parameters.putAll(authorizationRequest.getAuthorizationParameters()); + + parameters.clear(); + parameters.put(OAuth2Utils.CLIENT_ID, "foo"); + parameters.put(OAuth2Utils.SCOPE, "scope"); + OAuth2Request storedOAuth2Request = RequestTokenFactory.createOAuth2Request(parameters, "foo", true, Collections.singleton("scope")); + + String code = authorizationCodeServices.createAuthorizationCode(new OAuth2Authentication( + storedOAuth2Request, userAuthentication)); + parameters.putAll(storedOAuth2Request.getRequestParameters()); parameters.put("code", code); - authorizationRequest.setAuthorizationParameters(parameters); + + TokenRequest tokenRequest = requestFactory.createTokenRequest(parameters, client); + AuthorizationCodeTokenGranter granter = new AuthorizationCodeTokenGranter(providerTokenServices, - authorizationCodeServices, clientDetailsService); - OAuth2AccessToken token = granter.grant("authorization_code", authorizationRequest); + authorizationCodeServices, clientDetailsService, requestFactory); + OAuth2AccessToken token = granter.grant("authorization_code", tokenRequest); assertTrue(providerTokenServices.loadAuthentication(token.getValue()).isAuthenticated()); } @Test public void testAuthorizationParametersPreserved() { - DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest( - commaDelimitedStringToMap("foo=bar,client_id=foo")); - authorizationRequest.setApproved(true); + + parameters.clear(); + parameters.put("foo", "bar"); + parameters.put(OAuth2Utils.CLIENT_ID, "foo"); + parameters.put(OAuth2Utils.SCOPE, "scope"); + OAuth2Request storedOAuth2Request = RequestTokenFactory.createOAuth2Request(parameters, "foo", true, Collections.singleton("scope")); + Authentication userAuthentication = new UsernamePasswordAuthenticationToken("marissa", "koala", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); - String code = authorizationCodeServices.createAuthorizationCode(new AuthorizationRequestHolder( - authorizationRequest, userAuthentication)); - parameters.putAll(authorizationRequest.getAuthorizationParameters()); + String code = authorizationCodeServices.createAuthorizationCode(new OAuth2Authentication( + storedOAuth2Request, userAuthentication)); + parameters.put("code", code); - authorizationRequest.setAuthorizationParameters(parameters); + TokenRequest tokenRequest = requestFactory.createTokenRequest(parameters, client); + AuthorizationCodeTokenGranter granter = new AuthorizationCodeTokenGranter(providerTokenServices, - authorizationCodeServices, clientDetailsService); - OAuth2AccessToken token = granter.grant("authorization_code", authorizationRequest); - AuthorizationRequest finalRequest = providerTokenServices.loadAuthentication(token.getValue()) - .getAuthorizationRequest(); - assertEquals(code, finalRequest.getAuthorizationParameters().get("code")); - assertEquals("bar", finalRequest.getAuthorizationParameters().get("foo")); + authorizationCodeServices, clientDetailsService, requestFactory); + OAuth2AccessToken token = granter.grant("authorization_code", tokenRequest); + OAuth2Request finalRequest = providerTokenServices.loadAuthentication(token.getValue()) + .getOAuth2Request(); + assertEquals(code, finalRequest.getRequestParameters().get("code")); + assertEquals("bar", finalRequest.getRequestParameters().get("foo")); } @Test public void testAuthorizationRequestPreserved() { - DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest( - commaDelimitedStringToMap("client_id=foo,scope=read")); - authorizationRequest.setResourceIds(Collections.singleton("resource")); - authorizationRequest.setApproved(true); + + parameters.clear(); + parameters.put(OAuth2Utils.CLIENT_ID, "foo"); + parameters.put(OAuth2Utils.SCOPE, "read"); + OAuth2Request storedOAuth2Request = RequestTokenFactory.createOAuth2Request(parameters, "foo", null, true, Collections.singleton("read"), Collections.singleton("resource"), null, null, null); + Authentication userAuthentication = new UsernamePasswordAuthenticationToken("marissa", "koala", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); - String code = authorizationCodeServices.createAuthorizationCode(new AuthorizationRequestHolder( - authorizationRequest, userAuthentication)); - parameters.put("client_id", "foo"); + String code = authorizationCodeServices.createAuthorizationCode(new OAuth2Authentication( + storedOAuth2Request, userAuthentication)); + parameters.put("code", code); - authorizationRequest.setAuthorizationParameters(parameters); + // Ensure even if token request asks for more scope they are not granted + parameters.put(OAuth2Utils.SCOPE, "read write"); + TokenRequest tokenRequest = requestFactory.createTokenRequest(parameters, client); + AuthorizationCodeTokenGranter granter = new AuthorizationCodeTokenGranter(providerTokenServices, - authorizationCodeServices, clientDetailsService); - OAuth2AccessToken token = granter.grant("authorization_code", authorizationRequest); - AuthorizationRequest finalRequest = providerTokenServices.loadAuthentication(token.getValue()) - .getAuthorizationRequest(); + authorizationCodeServices, clientDetailsService, requestFactory); + OAuth2AccessToken token = granter.grant("authorization_code", tokenRequest); + OAuth2Request finalRequest = providerTokenServices.loadAuthentication(token.getValue()) + .getOAuth2Request(); assertEquals("[read]", finalRequest.getScope().toString()); assertEquals("[resource]", finalRequest.getResourceIds().toString()); assertTrue(finalRequest.isApproved()); } - private static Map commaDelimitedStringToMap(String string) { - Map result = new HashMap(); - for (String entry : StringUtils.commaDelimitedListToSet(string)) { - String[] values = StringUtils.delimitedListToStringArray(entry, "="); - result.put(values[0], values.length < 2 ? null : values[1]); - } - return result; - } - @Test public void testAuthorizationCodeGrantWithNoClientAuthorities() { - client.setAuthorities(Collections. emptySet()); - DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest("foo", - Arrays.asList("scope")); - authorizationRequest.setApproved(true); + + parameters.clear(); + parameters.put(OAuth2Utils.CLIENT_ID, "foo"); + parameters.put(OAuth2Utils.SCOPE, "scope"); + OAuth2Request storedOAuth2Request = RequestTokenFactory.createOAuth2Request(parameters, "foo", Collections. emptySet(), true, Collections.singleton("scope"), null, null, null, null); + Authentication userAuthentication = new UsernamePasswordAuthenticationToken("marissa", "koala", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); - String code = authorizationCodeServices.createAuthorizationCode(new AuthorizationRequestHolder( - authorizationRequest, userAuthentication)); - parameters.putAll(authorizationRequest.getAuthorizationParameters()); + String code = authorizationCodeServices.createAuthorizationCode(new OAuth2Authentication( + storedOAuth2Request, userAuthentication)); parameters.put("code", code); - authorizationRequest.setAuthorizationParameters(parameters); + TokenRequest tokenRequest = requestFactory.createTokenRequest(parameters, client); AuthorizationCodeTokenGranter granter = new AuthorizationCodeTokenGranter(providerTokenServices, - authorizationCodeServices, clientDetailsService); - OAuth2AccessToken token = granter.grant("authorization_code", authorizationRequest); + authorizationCodeServices, clientDetailsService, requestFactory); + OAuth2AccessToken token = granter.grant("authorization_code", tokenRequest); assertTrue(providerTokenServices.loadAuthentication(token.getValue()).isAuthenticated()); } @Test public void testAuthorizationRedirectMismatch() { Map initialParameters = new HashMap(); - initialParameters.put(REDIRECT_URI, "https://redirectme/"); - DefaultAuthorizationRequest initialRequest = new DefaultAuthorizationRequest(initialParameters); + initialParameters.put(OAuth2Utils.REDIRECT_URI, "https://redirectme/"); + //AuthorizationRequest initialRequest = createFromParameters(initialParameters); // we fake a valid resolvedRedirectUri because without the client would never come this far - initialRequest.setRedirectUri(initialParameters.get(REDIRECT_URI)); + //initialRequest.setRedirectUri(initialParameters.get(REDIRECT_URI)); + parameters.clear(); + parameters.put(OAuth2Utils.REDIRECT_URI, "https://redirectme/"); + parameters.put(OAuth2Utils.CLIENT_ID, "foo"); + OAuth2Request storedOAuth2Request = RequestTokenFactory.createOAuth2Request(parameters, "foo", null, true, null, null, "https://redirectme/", null, null); + Authentication userAuthentication = new UsernamePasswordAuthenticationToken("marissa", "koala", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); - String code = authorizationCodeServices.createAuthorizationCode(new AuthorizationRequestHolder(initialRequest, + String code = authorizationCodeServices.createAuthorizationCode(new OAuth2Authentication(storedOAuth2Request, userAuthentication)); Map authorizationParameters = new HashMap(); authorizationParameters.put("code", code); - DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest(initialParameters); - authorizationRequest.setAuthorizationParameters(authorizationParameters); + + //AuthorizationRequest oAuth2Request = createFromParameters(initialParameters); + //oAuth2Request.setRequestParameters(authorizationParameters); + TokenRequest tokenRequest = requestFactory.createTokenRequest(parameters, client); + tokenRequest.setRequestParameters(authorizationParameters); + AuthorizationCodeTokenGranter granter = new AuthorizationCodeTokenGranter(providerTokenServices, - authorizationCodeServices, clientDetailsService); + authorizationCodeServices, clientDetailsService, requestFactory); try { - granter.getOAuth2Authentication(authorizationRequest); + granter.getOAuth2Authentication(client, tokenRequest); fail("RedirectMismatchException because of null redirect_uri in authorizationRequest"); } catch (RedirectMismatchException e) { diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestInMemoryAuthorizationCodeServices.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/InMemoryAuthorizationCodeServicesTests.java similarity index 80% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestInMemoryAuthorizationCodeServices.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/InMemoryAuthorizationCodeServicesTests.java index 15a35ff20..6116332f1 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestInMemoryAuthorizationCodeServices.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/InMemoryAuthorizationCodeServicesTests.java @@ -2,7 +2,7 @@ import org.junit.Before; -public class TestInMemoryAuthorizationCodeServices extends TestAuthorizationCodeServicesBase { +public class InMemoryAuthorizationCodeServicesTests extends AuthorizationCodeServicesBaseTests { private InMemoryAuthorizationCodeServices authorizationCodeServices; diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/JdbcAuthorizationCodeServicesTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/JdbcAuthorizationCodeServicesTests.java new file mode 100644 index 000000000..76a5e34c8 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/JdbcAuthorizationCodeServicesTests.java @@ -0,0 +1,99 @@ +package org.springframework.security.oauth2.provider.code; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.ArrayList; +import java.util.List; + +import org.company.oauth2.CustomAuthentication; +import org.company.oauth2.CustomOAuth2Authentication; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.security.oauth2.common.util.SerializationStrategy; +import org.springframework.security.oauth2.common.util.SerializationUtils; +import org.springframework.security.oauth2.common.util.WhitelistedSerializationStrategy; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.RequestTokenFactory; + +public class JdbcAuthorizationCodeServicesTests extends AuthorizationCodeServicesBaseTests { + private JdbcAuthorizationCodeServices authorizationCodeServices; + + private EmbeddedDatabase db; + + @Before + public void setUp() throws Exception { + // creates a HSQL in-memory db populated from default scripts classpath:schema.sql and classpath:data.sql + db = new EmbeddedDatabaseBuilder().addDefaultScripts().build(); + authorizationCodeServices = new JdbcAuthorizationCodeServices(db); + } + + @After + public void tearDown() throws Exception { + db.shutdown(); + } + + @Override + AuthorizationCodeServices getAuthorizationCodeServices() { + return authorizationCodeServices; + } + + @Test + public void testCustomImplementation() { + OAuth2Request storedOAuth2Request = RequestTokenFactory.createOAuth2Request("id", false); + OAuth2Authentication expectedAuthentication = new CustomOAuth2Authentication(storedOAuth2Request, + new CustomAuthentication("test2", false)); + String code = getAuthorizationCodeServices().createAuthorizationCode(expectedAuthentication); + assertNotNull(code); + OAuth2Authentication actualAuthentication = getAuthorizationCodeServices().consumeAuthorizationCode(code); + assertEquals(expectedAuthentication, actualAuthentication); + } + + @Test(expected = IllegalArgumentException.class) + public void testNotAllowedCustomImplementation() { + OAuth2Request storedOAuth2Request = RequestTokenFactory.createOAuth2Request("id", false); + OAuth2Authentication expectedAuthentication = new CustomOAuth2Authentication(storedOAuth2Request, + new CustomAuthentication("test2", false)); + WhitelistedSerializationStrategy newStrategy = new WhitelistedSerializationStrategy(); + SerializationStrategy oldStrategy = SerializationUtils.getSerializationStrategy(); + try { + SerializationUtils.setSerializationStrategy(newStrategy); + String code = getAuthorizationCodeServices().createAuthorizationCode(expectedAuthentication); + assertNotNull(code); + getAuthorizationCodeServices().consumeAuthorizationCode(code); + } finally { + SerializationUtils.setSerializationStrategy(oldStrategy); + } + } + + @Test + public void testCustomImplementationWithCustomStrategy() { + OAuth2Request storedOAuth2Request = RequestTokenFactory.createOAuth2Request("id", false); + OAuth2Authentication expectedAuthentication = new CustomOAuth2Authentication(storedOAuth2Request, + new CustomAuthentication("test3", false)); + + AuthorizationCodeServices jdbcAuthorizationCodeServices = getAuthorizationCodeServices(); + List allowedClasses = new ArrayList(); + allowedClasses.add("java.util."); + allowedClasses.add("org.springframework.security."); + allowedClasses.add("org.company.oauth2.CustomOAuth2AccessToken"); + allowedClasses.add("org.company.oauth2.CustomOAuth2Authentication"); + allowedClasses.add("org.company.oauth2.CustomAuthentication"); + WhitelistedSerializationStrategy newStrategy = new WhitelistedSerializationStrategy(allowedClasses); + SerializationStrategy oldStrategy = SerializationUtils.getSerializationStrategy(); + try { + SerializationUtils.setSerializationStrategy(newStrategy); + String code = jdbcAuthorizationCodeServices.createAuthorizationCode(expectedAuthentication); + assertNotNull(code); + + OAuth2Authentication actualAuthentication = getAuthorizationCodeServices().consumeAuthorizationCode(code); + assertEquals(expectedAuthentication, actualAuthentication); + } finally { + SerializationUtils.setSerializationStrategy(oldStrategy); + } + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/RedisAuthorizationCodeServicesTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/RedisAuthorizationCodeServicesTests.java new file mode 100644 index 000000000..815fc260f --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/RedisAuthorizationCodeServicesTests.java @@ -0,0 +1,106 @@ +/* + * 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.provider.code; + +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.RequestTokenFactory; + +import org.springframework.util.ClassUtils; +import redis.clients.jedis.JedisShardInfo; + +/** + * @author Stefan Rempfer + */ +public class RedisAuthorizationCodeServicesTests { + + private RedisAuthorizationCodeServices authorizationCodeServices; + + private OAuth2Authentication authentication; + + /** + * Initialize test data and Class-Under-Test. + */ + @Before + public void setup() { + boolean springDataRedis_2_0 = ClassUtils.isPresent( + "org.springframework.data.redis.connection.RedisStandaloneConfiguration", + this.getClass().getClassLoader()); + + JedisConnectionFactory connectionFactory; + if (springDataRedis_2_0) { + connectionFactory = new JedisConnectionFactory(); + } else { + JedisShardInfo shardInfo = new JedisShardInfo("localhost"); + connectionFactory = new JedisConnectionFactory(shardInfo); + } + + authorizationCodeServices = new RedisAuthorizationCodeServices(connectionFactory); + + authentication = new OAuth2Authentication(RequestTokenFactory.createOAuth2Request("myClientId", false), + new TestingAuthenticationToken("myUser4Test", false)); + } + + /** + * Verifies that a authorization code could be generated and stored. + */ + @Test + public void verifyCreateAuthorizationCode() { + String authorizationCode1 = authorizationCodeServices.createAuthorizationCode(authentication); + assertNotNull("Authorization code must not be null!", authorizationCode1); + + String authorizationCode2 = authorizationCodeServices.createAuthorizationCode(authentication); + assertNotNull("Authorization code must not be null!", authorizationCode2); + + assertNotEquals("Authorization code must be different!", authorizationCode1, authorizationCode2); + } + + /** + * Verifies that a authorization code could be retrieved and removed. + */ + @Test + public void verifyCreateAndConsumeAuthorizationCode() { + + String authorizationCode = authorizationCodeServices.createAuthorizationCode(authentication); + assertNotNull("Authorization code must not be null!", authorizationCode); + + OAuth2Authentication authentication = authorizationCodeServices.consumeAuthorizationCode(authorizationCode); + assertNotSame("Authentication object must not be the same!", this.authentication, authentication); + assertEquals("Authentication object must equals to original one!", this.authentication, authentication); + + try { + authorizationCodeServices.consumeAuthorizationCode(authorizationCode); + fail("There must be an exception that the authorization code is invalid!"); + } + catch (InvalidGrantException e) { + assertThat("Wrong error message!", e.getMessage(), + allOf(containsString("Invalid authorization code"))); + } + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/SubdomainRedirectResolverTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/SubdomainRedirectResolverTests.java new file mode 100644 index 000000000..901c03dc4 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/SubdomainRedirectResolverTests.java @@ -0,0 +1,49 @@ +package org.springframework.security.oauth2.provider.code; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.endpoint.DefaultRedirectResolver; + +public class SubdomainRedirectResolverTests +{ + private DefaultRedirectResolver resolver; + private final BaseClientDetails client = new BaseClientDetails(); + + { + client.setAuthorizedGrantTypes(Collections.singleton("authorization_code")); + } + + @Before + public void setup() { + resolver = new DefaultRedirectResolver(); + } + + @Test + public void testRedirectMatch() throws Exception + { + resolver.setMatchSubdomains(true); + Set redirectUris = new HashSet(Arrays.asList("https://watchdox.com/")); + client.setRegisteredRedirectUri(redirectUris); + String requestedRedirect = "https://anywhere.watchdox.com/"; + assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); + } + + @Test(expected=RedirectMismatchException.class) + public void testRedirectNoMatch() throws Exception + { + Set redirectUris = new HashSet(Arrays.asList("https://watchdox.com/")); + client.setRegisteredRedirectUri(redirectUris); + String requestedRedirect = "https://anywhere.google.com/"; + assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestAuthorizationRequestHolder.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestAuthorizationRequestHolder.java deleted file mode 100644 index c98cdbd7a..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestAuthorizationRequestHolder.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Cloud Foundry 2012.02.03 Beta - * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - */ - -package org.springframework.security.oauth2.provider.code; - -import static org.junit.Assert.assertEquals; - -import java.util.Arrays; - -import org.junit.Test; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.oauth2.common.util.SerializationUtils; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; - -/** - * @author Dave Syer - * - */ -public class TestAuthorizationRequestHolder { - - private AuthorizationRequestHolder holder = new AuthorizationRequestHolder(new DefaultAuthorizationRequest( - "client", Arrays.asList("read")), new UsernamePasswordAuthenticationToken("user", "pwd")); - - @Test - public void test() { - AuthorizationRequestHolder other = SerializationUtils.deserialize(SerializationUtils.serialize(holder)); - assertEquals(holder, other); - } - -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestDefaultRedirectResolver.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestDefaultRedirectResolver.java deleted file mode 100644 index f0ab68de4..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestDefaultRedirectResolver.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2006-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package org.springframework.security.oauth2.provider.code; - -import static org.junit.Assert.assertEquals; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import org.junit.Test; -import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; -import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException; -import org.springframework.security.oauth2.provider.BaseClientDetails; -import org.springframework.security.oauth2.provider.endpoint.DefaultRedirectResolver; - -/** - * @author Dave Syer - */ -public class TestDefaultRedirectResolver { - - private DefaultRedirectResolver resolver = new DefaultRedirectResolver(); - - private BaseClientDetails client = new BaseClientDetails(); - - { - client.setAuthorizedGrantTypes(Collections.singleton("authorization_code")); - } - - @Test - public void testRedirectMatchesRegisteredValue() throws Exception { - Set redirectUris = new HashSet(Arrays.asList("http://anywhere.com/")); - client.setRegisteredRedirectUri(redirectUris); - String requestedRedirect = "http://anywhere.com/myendpoint"; - assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); - } - - @Test - public void testRedirectWithNoRegisteredValue() throws Exception { - String requestedRedirect = "http://anywhere.com/myendpoint"; - assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); - } - - // If only one redirect has been registered, then we should use it - @Test - public void testRedirectWithNoRequestedValue() throws Exception { - Set redirectUris = new HashSet(Arrays.asList("http://anywhere.com/")); - client.setRegisteredRedirectUri(redirectUris); - resolver.resolveRedirect(null, client); - } - - // If multiple redirects registered, then we should get an exception - @Test(expected = RedirectMismatchException.class) - public void testRedirectWithNoRequestedValueAndMultipleRegistered() throws Exception { - Set redirectUris = new HashSet(Arrays.asList("http://anywhere.com/", "http://nowhere.com/")); - client.setRegisteredRedirectUri(redirectUris); - resolver.resolveRedirect(null, client); - } - - @Test(expected = InvalidGrantException.class) - public void testNoGrantType() throws Exception { - Set redirectUris = new HashSet(Arrays.asList("http://anywhere.com/", "http://nowhere.com/")); - client.setRegisteredRedirectUri(redirectUris); - client.setAuthorizedGrantTypes(Collections.emptyList()); - resolver.resolveRedirect(null, client); - } - - @Test(expected = InvalidGrantException.class) - public void testWrongGrantType() throws Exception { - Set redirectUris = new HashSet(Arrays.asList("http://anywhere.com/", "http://nowhere.com/")); - client.setRegisteredRedirectUri(redirectUris); - client.setAuthorizedGrantTypes(Collections.singleton("client_credentials")); - resolver.resolveRedirect(null, client); - } - - @Test(expected = InvalidGrantException.class) - public void testWrongCustomGrantType() throws Exception { - resolver.setRedirectGrantTypes(Collections.singleton("foo")); - Set redirectUris = new HashSet(Arrays.asList("http://anywhere.com/", "http://nowhere.com/")); - client.setRegisteredRedirectUri(redirectUris); - resolver.resolveRedirect(null, client); - } - - @Test(expected = RedirectMismatchException.class) - public void testRedirectNotMatching() throws Exception { - Set redirectUris = new HashSet(Arrays.asList("http://nowhere.com/")); - String requestedRedirect = "http://anywhere.com/myendpoint"; - client.setRegisteredRedirectUri(redirectUris); - assertEquals(redirectUris.iterator().next(), resolver.resolveRedirect(requestedRedirect, client)); - } - -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestJdbcAuthorizationCodeServices.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestJdbcAuthorizationCodeServices.java deleted file mode 100644 index a9eb09e99..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestJdbcAuthorizationCodeServices.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.springframework.security.oauth2.provider.code; - -import org.junit.After; -import org.junit.Before; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; - -public class TestJdbcAuthorizationCodeServices extends TestAuthorizationCodeServicesBase { - private JdbcAuthorizationCodeServices authorizationCodeServices; - - private EmbeddedDatabase db; - - @Before - public void setUp() throws Exception { - // creates a HSQL in-memory db populated from default scripts classpath:schema.sql and classpath:data.sql - db = new EmbeddedDatabaseBuilder().addDefaultScripts().build(); - authorizationCodeServices = new JdbcAuthorizationCodeServices(db); - } - - @After - public void tearDown() throws Exception { - db.shutdown(); - } - - @Override - AuthorizationCodeServices getAuthorizationCodeServices() { - return authorizationCodeServices; - } -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpointTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpointTests.java new file mode 100644 index 000000000..383f025b5 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpointTests.java @@ -0,0 +1,682 @@ +/* + * Copyright 2006-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.provider.endpoint; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.AUTHORIZATION_REQUEST_ATTR_NAME; +import static org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; +import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.TokenGranter; +import org.springframework.security.oauth2.provider.TokenRequest; +import org.springframework.security.oauth2.provider.approval.ApprovalStoreUserApprovalHandler; +import org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler; +import org.springframework.security.oauth2.provider.approval.InMemoryApprovalStore; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.support.SimpleSessionStatus; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.view.RedirectView; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * @author Dave Syer + * + */ +public class AuthorizationEndpointTests { + + private AuthorizationEndpoint endpoint = new AuthorizationEndpoint(); + + private HashMap model = new HashMap(); + + private SimpleSessionStatus sessionStatus = new SimpleSessionStatus(); + + private UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken("foo", "bar", + Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))); + + private BaseClientDetails client; + + private AuthorizationRequest getAuthorizationRequest(String clientId, String redirectUri, String state, + String scope, Set responseTypes) { + HashMap parameters = new HashMap(); + parameters.put(OAuth2Utils.CLIENT_ID, clientId); + if (redirectUri != null) { + parameters.put(OAuth2Utils.REDIRECT_URI, redirectUri); + } + if (state != null) { + parameters.put(OAuth2Utils.STATE, state); + } + if (scope != null) { + parameters.put(OAuth2Utils.SCOPE, scope); + } + if (responseTypes != null) { + parameters.put(OAuth2Utils.RESPONSE_TYPE, OAuth2Utils.formatParameterList(responseTypes)); + } + return new AuthorizationRequest(parameters, Collections. emptyMap(), + parameters.get(OAuth2Utils.CLIENT_ID), + OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)), null, null, false, + parameters.get(OAuth2Utils.STATE), parameters.get(OAuth2Utils.REDIRECT_URI), + OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.RESPONSE_TYPE))); + } + + @Before + public void init() throws Exception { + client = new BaseClientDetails(); + client.setRegisteredRedirectUri(Collections.singleton("https://anywhere.com/")); + client.setAuthorizedGrantTypes(Arrays.asList("authorization_code", "implicit")); + endpoint.setClientDetailsService(new ClientDetailsService() { + public ClientDetails loadClientByClientId(String clientId) throws OAuth2Exception { + return client; + } + }); + endpoint.setTokenGranter(new TokenGranter() { + public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { + return null; + } + }); + endpoint.setRedirectResolver(new DefaultRedirectResolver()); + endpoint.afterPropertiesSet(); + } + + @Test(expected = IllegalStateException.class) + public void testMandatoryProperties() throws Exception { + endpoint = new AuthorizationEndpoint(); + endpoint.afterPropertiesSet(); + } + + @Test + public void testStartAuthorizationCodeFlow() throws Exception { + ModelAndView result = endpoint.authorize(model, + getAuthorizationRequest("foo", null, null, "read", Collections.singleton("code")) + .getRequestParameters(), sessionStatus, principal); + assertEquals("forward:/oauth/confirm_access", result.getViewName()); + } + + @Test + public void testApprovalStoreAddsScopes() throws Exception { + ApprovalStoreUserApprovalHandler userApprovalHandler = new ApprovalStoreUserApprovalHandler(); + userApprovalHandler.setApprovalStore(new InMemoryApprovalStore()); + endpoint.setUserApprovalHandler(userApprovalHandler); + ModelAndView result = endpoint.authorize(model, + getAuthorizationRequest("foo", null, null, "read", Collections.singleton("code")) + .getRequestParameters(), sessionStatus, principal); + assertEquals("forward:/oauth/confirm_access", result.getViewName()); + assertTrue(result.getModel().containsKey("scopes")); + } + + @Test(expected = OAuth2Exception.class) + public void testStartAuthorizationCodeFlowForClientCredentialsFails() throws Exception { + client.setAuthorizedGrantTypes(Collections.singleton("client_credentials")); + ModelAndView result = endpoint.authorize(model, + getAuthorizationRequest("foo", null, null, null, Collections.singleton("code")).getRequestParameters(), + sessionStatus, principal); + assertEquals("forward:/oauth/confirm_access", result.getViewName()); + } + + @Test + public void testAuthorizationCodeWithFragment() throws Exception { + endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); + AuthorizationRequest request = getAuthorizationRequest("foo", "https://anywhere.com/#bar", null, null, Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request)); + View result = endpoint.approveOrDeny(Collections.singletonMap(OAuth2Utils.USER_OAUTH_APPROVAL, "true"), model, + sessionStatus, principal); + assertEquals("https://anywhere.com/?code=thecode#bar", ((RedirectView) result).getUrl()); + } + + @Test + public void testAuthorizationCodeWithQueryParams() throws Exception { + endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); + AuthorizationRequest request = getAuthorizationRequest("foo", "https://anywhere.com/?foo=bar", null, null, Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request)); + View result = endpoint.approveOrDeny(Collections.singletonMap(OAuth2Utils.USER_OAUTH_APPROVAL, "true"), model, + sessionStatus, principal); + assertEquals("https://anywhere.com/?foo=bar&code=thecode", ((RedirectView) result).getUrl()); + } + + @Test + public void testAuthorizationCodeWithTrickyState() throws Exception { + endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); + AuthorizationRequest request = getAuthorizationRequest("foo", "https://anywhere.com/", " =?s", null, Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request)); + View result = endpoint.approveOrDeny(Collections.singletonMap(OAuth2Utils.USER_OAUTH_APPROVAL, "true"), model, + sessionStatus, principal); + assertEquals("https://anywhere.com/?code=thecode&state=%20%3D?s", ((RedirectView) result).getUrl()); + } + + @Test + public void testAuthorizationCodeWithMultipleQueryParams() throws Exception { + endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); + AuthorizationRequest request = getAuthorizationRequest("foo", "https://anywhere.com/?foo=bar&bar=foo", null, null, + Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request)); + View result = endpoint.approveOrDeny(Collections.singletonMap(OAuth2Utils.USER_OAUTH_APPROVAL, "true"), model, + sessionStatus, principal); + assertEquals("https://anywhere.com/?foo=bar&bar=foo&code=thecode", ((RedirectView) result).getUrl()); + } + + @Test + public void testAuthorizationCodeWithTrickyQueryParams() throws Exception { + endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); + AuthorizationRequest request = getAuthorizationRequest("foo", "https://anywhere.com/?foo=b%20=&bar=f%20$", null, null, + Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request)); + View result = endpoint.approveOrDeny(Collections.singletonMap(OAuth2Utils.USER_OAUTH_APPROVAL, "true"), model, + sessionStatus, principal); + String url = ((RedirectView) result).getUrl(); + assertEquals("https://anywhere.com/?foo=b%20=&bar=f%20https://anywhere.com?foo=b%20=&bar=f%20$&code=thecodecode=thecode", url); + MultiValueMap params = UriComponentsBuilder.fromHttpUrl(url).build().getQueryParams(); + assertEquals("[b%20=]", params.get("foo").toString()); + assertEquals("[f%20$]", params.get("bar").toString()); + } + + @Test + public void testAuthorizationCodeWithTrickyEncodedQueryParams() throws Exception { + endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); + AuthorizationRequest request = getAuthorizationRequest( + "foo", "https://anywhere.com/path?foo=b%20%3D&bar=f%20$", null, null, Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request)); + View result = endpoint.approveOrDeny(Collections.singletonMap(OAuth2Utils.USER_OAUTH_APPROVAL, "true"), model, + sessionStatus, principal); + assertEquals("https://anywhere.com/path?foo=b%20%3D&bar=f%20https://anywhere.com/path?foo=b%20%3D&bar=f%20$&code=thecodecode=thecode", ((RedirectView) result).getUrl()); + } + + @Test + public void testAuthorizationCodeWithMoreTrickyEncodedQueryParams() throws Exception { + endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); + AuthorizationRequest request = getAuthorizationRequest( + "foo", "https://anywhere/?t=a%3Db%26ep%3Dtest%2540test.me", null, null, Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request)); + View result = endpoint.approveOrDeny(Collections.singletonMap(OAuth2Utils.USER_OAUTH_APPROVAL, "true"), model, + sessionStatus, principal); + assertEquals("https://anywhere/?t=a%3Db%26ep%3Dtest%2540test.me&code=thecode", ((RedirectView) result).getUrl()); + } + + @Test + public void testAuthorizationCodeError() throws Exception { + endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() { + public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return authorizationRequest; + } + + public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return authorizationRequest; + } + + public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + return true; + } + }); + endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices() { + @Override + public String createAuthorizationCode(OAuth2Authentication authentication) { + throw new InvalidScopeException("FOO"); + } + }); + ModelAndView result = endpoint.authorize( + model, + getAuthorizationRequest("foo", "https://anywhere.com/", "mystate", "myscope", + Collections.singleton("code")).getRequestParameters(), sessionStatus, principal); + String url = ((RedirectView) result.getView()).getUrl(); + assertTrue("Wrong view: " + result, url.startsWith("https://anywhere.com/")); + assertTrue("No error: " + result, url.contains("?error=")); + assertTrue("Wrong state: " + result, url.contains("&state=mystate")); + + } + + @Test + public void testAuthorizationCodeWithMultipleResponseTypes() throws Exception { + Set responseTypes = new HashSet(); + responseTypes.add("code"); + responseTypes.add("other"); + ModelAndView result = endpoint.authorize(model, + getAuthorizationRequest("foo", null, null, "read", responseTypes).getRequestParameters(), + sessionStatus, principal); + assertEquals("forward:/oauth/confirm_access", result.getViewName()); + } + + @Test + public void testImplicitPreApproved() throws Exception { + endpoint.setTokenGranter(new TokenGranter() { + + public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO"); + token.setAdditionalInformation(Collections.singletonMap("foo", (Object) "bar")); + return token; + + } + }); + endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() { + public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return authorizationRequest; + } + + public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return authorizationRequest; + } + + public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + return true; + } + }); + AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com/", "mystate", + "myscope", Collections.singleton("token")); + ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus, + principal); + String url = ((RedirectView) result.getView()).getUrl(); + assertTrue("Wrong view: " + result, url.startsWith("https://anywhere.com/")); + assertTrue("Wrong state: " + result, url.contains("&state=mystate")); + assertTrue("Wrong token: " + result, url.contains("access_token=")); + assertTrue("Wrong token: " + result, url.contains("foo=bar")); + } + + @Test + public void testImplicitAppendsScope() throws Exception { + endpoint.setTokenGranter(new TokenGranter() { + public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO"); + token.setScope(Collections.singleton("read")); + return token; + } + }); + endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() { + public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return authorizationRequest; + } + + public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return authorizationRequest; + } + + public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + return true; + } + }); + AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com/", "mystate", + "myscope", Collections.singleton("token")); + ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus, + principal); + String url = ((RedirectView) result.getView()).getUrl(); + assertTrue("Wrong scope: " + result, url.contains("&scope=read")); + } + + @Test + public void testImplicitWithQueryParam() throws Exception { + endpoint.setTokenGranter(new TokenGranter() { + public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO"); + return token; + } + }); + endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() { + public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + return true; + } + }); + AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com/?foo=bar", + "mystate", "myscope", Collections.singleton("token")); + ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus, + principal); + String url = ((RedirectView) result.getView()).getUrl(); + assertTrue("Wrong url: " + result, url.contains("foo=bar")); + } + + @Test + public void testImplicitWithAdditionalInfo() throws Exception { + endpoint.setTokenGranter(new TokenGranter() { + public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO"); + token.setAdditionalInformation(Collections. singletonMap("foo", "bar")); + return token; + } + }); + endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() { + public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + return true; + } + }); + AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com/", "mystate", + "myscope", Collections.singleton("token")); + ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus, + principal); + String url = ((RedirectView) result.getView()).getUrl(); + assertTrue("Wrong url: " + result, url.contains("foo=bar")); + } + + @Test + public void testImplicitAppendsScopeWhenDefaulting() throws Exception { + endpoint.setTokenGranter(new TokenGranter() { + public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO"); + token.setScope(new LinkedHashSet(Arrays.asList("read", "write"))); + return token; + } + }); + endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() { + public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + return true; + } + + public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return authorizationRequest; + } + + public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return authorizationRequest; + } + }); + client.setScope(Collections.singleton("read")); + AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com/", "mystate", + null, Collections.singleton("token")); + ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus, + principal); + String url = ((RedirectView) result.getView()).getUrl(); + assertTrue("Wrong scope: " + result, url.contains("&scope=read%20write")); + } + + @Test(expected = InvalidScopeException.class) + public void testImplicitPreApprovedButInvalid() throws Exception { + endpoint.setTokenGranter(new TokenGranter() { + public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { + throw new IllegalStateException("Shouldn't be called"); + } + }); + endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() { + public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + return true; + } + + public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return authorizationRequest; + } + + public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return authorizationRequest; + } + }); + client.setScope(Collections.singleton("smallscope")); + AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com/", "mystate", + "bigscope", Collections.singleton("token")); + ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus, + principal); + String url = ((RedirectView) result.getView()).getUrl(); + assertTrue("Wrong view: " + result, url.startsWith("https://anywhere.com/")); + } + + @Test + public void testImplicitUnapproved() throws Exception { + endpoint.setTokenGranter(new TokenGranter() { + public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { + return null; + } + }); + AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com/", "mystate", + "myscope", Collections.singleton("token")); + ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus, + principal); + assertEquals("forward:/oauth/confirm_access", result.getViewName()); + } + + @Test + public void testImplicitError() throws Exception { + endpoint.setUserApprovalHandler(new DefaultUserApprovalHandler() { + public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return authorizationRequest; + } + + public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return authorizationRequest; + } + + public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + return true; + } + }); + endpoint.setTokenGranter(new TokenGranter() { + public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { + return null; + } + }); + AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "https://anywhere.com/", "mystate", + "myscope", Collections.singleton("token")); + ModelAndView result = endpoint.authorize(model, authorizationRequest.getRequestParameters(), sessionStatus, + principal); + + String url = ((RedirectView) result.getView()).getUrl(); + assertTrue("Wrong view: " + result, url.startsWith("https://anywhere.com/")); + assertTrue("No error: " + result, url.contains("#error=")); + assertTrue("Wrong state: " + result, url.contains("&state=mystate")); + + } + + @Test + public void testApproveOrDeny() throws Exception { + AuthorizationRequest request = getAuthorizationRequest("foo", "https://anywhere.com/", null, null, + Collections.singleton("code")); + request.setApproved(true); + Map approvalParameters = new HashMap(); + approvalParameters.put("user_oauth_approval", "true"); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request)); + View result = endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + assertTrue("Wrong view: " + result, ((RedirectView) result).getUrl().startsWith("https://anywhere.com/")); + } + + @Test + public void testApprovalDenied() throws Exception { + AuthorizationRequest request = getAuthorizationRequest("foo", "https://anywhere.com/", null, null, Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request)); + Map approvalParameters = new HashMap(); + approvalParameters.put("user_oauth_approval", "false"); + View result = endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + String url = ((RedirectView) result).getUrl(); + assertTrue("Wrong view: " + result, url.startsWith("https://anywhere.com/")); + assertTrue("Wrong view: " + result, url.contains("error=access_denied")); + } + + @Test + public void testDirectApproval() throws Exception { + ModelAndView result = endpoint.authorize(model, + getAuthorizationRequest("foo", "https://anywhere.com/", null, "read", Collections.singleton("code")) + .getRequestParameters(), sessionStatus, principal); + // Should go to approval page (SECOAUTH-191) + assertFalse(result.getView() instanceof RedirectView); + } + + @Test + public void testRedirectUriOptionalForAuthorization() throws Exception { + ModelAndView result = endpoint.authorize(model, + getAuthorizationRequest("foo", null, null, "read", Collections.singleton("code")) + .getRequestParameters(), sessionStatus, principal); + // RedirectUri parameter should be null (SECOAUTH-333), however the resolvedRedirectUri not + AuthorizationRequest authorizationRequest = (AuthorizationRequest) result.getModelMap().get( + AUTHORIZATION_REQUEST_ATTR_NAME); + assertNull(authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI)); + assertEquals("https://anywhere.com/", authorizationRequest.getRedirectUri()); + } + + /** + * Ensure that if the approval endpoint is called without a resolved redirect URI, the request fails. + * @throws Exception + */ + @Test(expected = InvalidRequestException.class) + public void testApproveOrDenyWithOAuth2RequestWithoutRedirectUri() throws Exception { + AuthorizationRequest request = getAuthorizationRequest("foo", null, null, null, Collections.singleton("code")); + request.setApproved(true); + Map approvalParameters = new HashMap(); + approvalParameters.put("user_oauth_approval", "true"); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, request); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(request)); + endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedClientId() throws Exception { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "https://anywhere.com/", "state-1234", "read", Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setClientId("bar"); // Modify authorization request + Map approvalParameters = new HashMap(); + approvalParameters.put("user_oauth_approval", "true"); + endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedState() throws Exception { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "https://anywhere.com/", "state-1234", "read", Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setState("state-5678"); // Modify authorization request + Map approvalParameters = new HashMap(); + approvalParameters.put("user_oauth_approval", "true"); + endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedRedirectUri() throws Exception { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "https://anywhere.com/", "state-1234", "read", Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setRedirectUri("https://somewhere.com/"); // Modify authorization request + Map approvalParameters = new HashMap(); + approvalParameters.put("user_oauth_approval", "true"); + endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedResponseTypes() throws Exception { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "https://anywhere.com/", "state-1234", "read", Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setResponseTypes(Collections.singleton("implicit")); // Modify authorization request + Map approvalParameters = new HashMap(); + approvalParameters.put("user_oauth_approval", "true"); + endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedScope() throws Exception { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "https://anywhere.com/", "state-1234", "read", Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setScope(Arrays.asList("read", "write")); // Modify authorization request + Map approvalParameters = new HashMap(); + approvalParameters.put("user_oauth_approval", "true"); + endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedApproved() throws Exception { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "https://anywhere.com/", "state-1234", "read", Collections.singleton("code")); + authorizationRequest.setApproved(false); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setApproved(true); // Modify authorization request + Map approvalParameters = new HashMap(); + approvalParameters.put("user_oauth_approval", "true"); + endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedResourceIds() throws Exception { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "https://anywhere.com/", "state-1234", "read", Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setResourceIds(Collections.singleton("resource-other")); // Modify authorization request + Map approvalParameters = new HashMap(); + approvalParameters.put("user_oauth_approval", "true"); + endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedAuthorities() throws Exception { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "https://anywhere.com/", "state-1234", "read", Collections.singleton("code")); + model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest); + model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, endpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("authority-other")); // Modify authorization request + Map approvalParameters = new HashMap(); + approvalParameters.put("user_oauth_approval", "true"); + endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + private class StubAuthorizationCodeServices implements AuthorizationCodeServices { + private OAuth2Authentication authentication; + + public String createAuthorizationCode(OAuth2Authentication authentication) { + this.authentication = authentication; + return "thecode"; + } + + public OAuth2Authentication consumeAuthorizationCode(String code) throws InvalidGrantException { + return authentication; + } + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/CheckTokenEndpointTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/CheckTokenEndpointTest.java new file mode 100644 index 000000000..125afb4b1 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/CheckTokenEndpointTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-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.provider.endpoint; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.AccessTokenConverter; +import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author Joe Grandja + */ +public class CheckTokenEndpointTest { + private CheckTokenEndpoint checkTokenEndpoint; + + @Before + public void setUp() { + ResourceServerTokenServices resourceServerTokenServices = mock(ResourceServerTokenServices.class); + OAuth2AccessToken accessToken = mock(OAuth2AccessToken.class); + OAuth2Authentication authentication = mock(OAuth2Authentication.class); + when(resourceServerTokenServices.readAccessToken(anyString())).thenReturn(accessToken); + when(accessToken.isExpired()).thenReturn(false); + when(accessToken.getValue()).thenReturn("access-token-1234"); + when(resourceServerTokenServices.loadAuthentication(accessToken.getValue())).thenReturn(authentication); + this.checkTokenEndpoint = new CheckTokenEndpoint(resourceServerTokenServices); + + AccessTokenConverter accessTokenConverter = mock(AccessTokenConverter.class); + when(accessTokenConverter.convertAccessToken(accessToken, authentication)).thenReturn(new HashMap()); + this.checkTokenEndpoint.setAccessTokenConverter(accessTokenConverter); + } + + // gh-1070 + @Test + public void checkTokenWhenTokenValidThenReturnActiveAttribute() throws Exception { + Map response = this.checkTokenEndpoint.checkToken("access-token-1234"); + Object active = response.get("active"); + assertNotNull("active is null", active); + assertEquals("active not true", Boolean.TRUE, active); + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/DefaultRedirectResolverTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/DefaultRedirectResolverTests.java new file mode 100644 index 000000000..026301b80 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/DefaultRedirectResolverTests.java @@ -0,0 +1,322 @@ +/* + * 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.provider.endpoint; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; +import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; + +/** + * @author Dave Syer + */ +public class DefaultRedirectResolverTests { + + private DefaultRedirectResolver resolver; + + private BaseClientDetails client; + + @Before + public void setup() { + client = new BaseClientDetails(); + client.setAuthorizedGrantTypes(Collections.singleton("authorization_code")); + resolver = new DefaultRedirectResolver(); + } + + @Test + public void testRedirectMatchesRegisteredValue() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/")); + client.setRegisteredRedirectUri(redirectUris); + String requestedRedirect = "https://anywhere.com/"; + assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); + } + + @Test(expected = InvalidRequestException.class) + public void testRedirectWithNoRegisteredValue() throws Exception { + String requestedRedirect = "https://anywhere.com/myendpoint"; + resolver.resolveRedirect(requestedRedirect, client); + } + + // If only one redirect has been registered, then we should use it + @Test + public void testRedirectWithNoRequestedValue() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/")); + client.setRegisteredRedirectUri(redirectUris); + resolver.resolveRedirect(null, client); + } + + // If multiple redirects registered, then we should get an exception + @Test(expected = RedirectMismatchException.class) + public void testRedirectWithNoRequestedValueAndMultipleRegistered() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/", "https://nowhere.com/")); + client.setRegisteredRedirectUri(redirectUris); + resolver.resolveRedirect(null, client); + } + + @Test(expected = InvalidGrantException.class) + public void testNoGrantType() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/", "https://nowhere.com/")); + client.setRegisteredRedirectUri(redirectUris); + client.setAuthorizedGrantTypes(Collections.emptyList()); + resolver.resolveRedirect(null, client); + } + + @Test(expected = InvalidGrantException.class) + public void testWrongGrantType() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/", "https://nowhere.com/")); + client.setRegisteredRedirectUri(redirectUris); + client.setAuthorizedGrantTypes(Collections.singleton("client_credentials")); + resolver.resolveRedirect(null, client); + } + + @Test(expected = InvalidGrantException.class) + public void testWrongCustomGrantType() throws Exception { + resolver.setRedirectGrantTypes(Collections.singleton("foo")); + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/", "https://nowhere.com/")); + client.setRegisteredRedirectUri(redirectUris); + resolver.resolveRedirect(null, client); + } + + @Test(expected = RedirectMismatchException.class) + public void testRedirectNotMatching() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://nowhere.com/")); + String requestedRedirect = "https://anywhere.com/myendpoint"; + client.setRegisteredRedirectUri(redirectUris); + assertEquals(redirectUris.iterator().next(), resolver.resolveRedirect(requestedRedirect, client)); + } + + @Test(expected = RedirectMismatchException.class) + public void testRedirectNotMatchingWithTraversal() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/foo")); + String requestedRedirect = "https://anywhere.com/bar"; + client.setRegisteredRedirectUri(redirectUris); + assertEquals(redirectUris.iterator().next(), resolver.resolveRedirect(requestedRedirect, client)); + } + + // gh-1331 + @Test(expected = RedirectMismatchException.class) + public void testRedirectNotMatchingWithHexEncodedTraversal() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/foo")); + client.setRegisteredRedirectUri(redirectUris); + String requestedRedirect = "https://anywhere.com/"; // hexadecimal encoding of '..' represents '%2E%2E' + resolver.resolveRedirect(requestedRedirect, client); + } + + // gh-747 + @Test(expected = RedirectMismatchException.class) + public void testRedirectNotMatchingSubdomain() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/foo")); + client.setRegisteredRedirectUri(redirectUris); + resolver.resolveRedirect("https://2anywhere.com/foo", client); + } + + // gh-747 + // gh-747 + @Test + public void testRedirectMatchingSubdomain() throws Exception { + resolver.setMatchSubdomains(true); + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/foo")); + String requestedRedirect = "https://2.anywhere.com/foo"; + client.setRegisteredRedirectUri(redirectUris); + assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); + } + + @Test(expected = RedirectMismatchException.class) + public void testRedirectMatchSubdomainsDefaultsFalse() { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/")); + client.setRegisteredRedirectUri(redirectUris); + resolver.resolveRedirect("https://2.anywhere.com/", client); + } + + // gh-746 + @Test(expected = RedirectMismatchException.class) + public void testRedirectNotMatchingPort() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com:90/")); + client.setRegisteredRedirectUri(redirectUris); + resolver.resolveRedirect("https://anywhere.com:91/foo", client); + } + + // gh-746 + @Test + public void testRedirectMatchingPort() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com:90/")); + client.setRegisteredRedirectUri(redirectUris); + String requestedRedirect = "https://anywhere.com:90/"; + assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); + } + + // gh-746 + @Test(expected = RedirectMismatchException.class) + public void testRedirectRegisteredPortSetRequestedPortNotSet() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com:90/")); + client.setRegisteredRedirectUri(redirectUris); + resolver.resolveRedirect("https://anywhere.com/foo", client); + } + + // gh-746 + @Test(expected = RedirectMismatchException.class) + public void testRedirectRegisteredPortNotSetRequestedPortSet() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/")); + client.setRegisteredRedirectUri(redirectUris); + resolver.resolveRedirect("https://anywhere.com:8443/foo", client); + } + + // gh-746 + @Test + public void testRedirectMatchPortsFalse() throws Exception { + resolver.setMatchPorts(false); + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com:90/")); + client.setRegisteredRedirectUri(redirectUris); + String requestedRedirect = "https://anywhere.com:91/"; + assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); + } + + // gh-1386 + @Test + public void testRedirectNotMatchingReturnsGenericErrorMessage() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://nowhere.com/")); + String requestedRedirect = "https://anywhere.com/myendpoint"; + client.setRegisteredRedirectUri(redirectUris); + try { + resolver.resolveRedirect(requestedRedirect, client); + fail(); + } catch (RedirectMismatchException ex) { + assertEquals("Invalid redirect uri does not match one of the registered values.", ex.getMessage()); + } + } + + // gh-1566 + @Test(expected = RedirectMismatchException.class) + public void testRedirectRegisteredUserInfoNotMatching() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://userinfo@anywhere.com/")); + client.setRegisteredRedirectUri(redirectUris); + resolver.resolveRedirect("https://otheruserinfo@anywhere.com/", client); + } + + // gh-1566 + @Test(expected = RedirectMismatchException.class) + public void testRedirectRegisteredNoUserInfoNotMatching() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://userinfo@anywhere.com/")); + client.setRegisteredRedirectUri(redirectUris); + resolver.resolveRedirect("https://anywhere.com/", client); + } + + // gh-1566 + @Test() + public void testRedirectRegisteredUserInfoMatching() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://userinfo@anywhere.com/")); + client.setRegisteredRedirectUri(redirectUris); + String requestedRedirect = "https://userinfo@anywhere.com/"; + assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); + } + + // gh-1566 + @Test() + public void testRedirectRegisteredFragmentIgnoredAndStripped() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://userinfo@anywhere.com/foo/bar#baz")); + client.setRegisteredRedirectUri(redirectUris); + String requestedRedirect = "https://userinfo@anywhere.com/foo/bar"; + assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect + "#bar", client)); + } + + // gh-1566 + @Test() + public void testRedirectRegisteredQueryParamsMatching() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/?p1=v1&p2=v2")); + client.setRegisteredRedirectUri(redirectUris); + String requestedRedirect = "https://anywhere.com/?p1=v1&p2=v2"; + assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); + } + + // gh-1566 + @Test() + public void testRedirectRegisteredQueryParamsMatchingIgnoringAdditionalParams() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/?p1=v1&p2=v2")); + client.setRegisteredRedirectUri(redirectUris); + String requestedRedirect = "https://anywhere.com/?p1=v1&p2=v2&p3=v3"; + assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); + } + + // gh-1566 + @Test() + public void testRedirectRegisteredQueryParamsMatchingDifferentOrder() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/?p1=v1&p2=v2")); + client.setRegisteredRedirectUri(redirectUris); + String requestedRedirect = "https://anywhere.com/?p2=v2&p1=v1"; + assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); + } + + // gh-1566 + @Test(expected = RedirectMismatchException.class) + public void testRedirectRegisteredQueryParamsWithDifferentValues() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/?p1=v1&p2=v2")); + client.setRegisteredRedirectUri(redirectUris); + resolver.resolveRedirect("https://anywhere.com/?p1=v1&p2=v3", client); + } + + // gh-1566 + @Test(expected = RedirectMismatchException.class) + public void testRedirectRegisteredQueryParamsNotMatching() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/?p1=v1")); + client.setRegisteredRedirectUri(redirectUris); + resolver.resolveRedirect("https://anywhere.com/?p2=v2", client); + } + + // gh-1566 + @Test(expected = RedirectMismatchException.class) + public void testRedirectRegisteredQueryParamsPartiallyMatching() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/?p1=v1&p2=v2")); + client.setRegisteredRedirectUri(redirectUris); + resolver.resolveRedirect("https://anywhere.com/?p2=v2&p3=v3", client); + } + + // gh-1566 + @Test + public void testRedirectRegisteredQueryParamsMatchingWithMultipleValuesInRegistered() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/?p1=v11&p1=v12")); + client.setRegisteredRedirectUri(redirectUris); + String requestedRedirect = "https://anywhere.com/?p1=v11&p1=v12"; + assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); + } + + // gh-1566 + @Test + public void testRedirectRegisteredQueryParamsMatchingWithParamWithNoValue() throws Exception { + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/?p1&p2=v2")); + client.setRegisteredRedirectUri(redirectUris); + String requestedRedirect = "https://anywhere.com/?p1&p2=v2"; + assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); + } + + // gh-1618 + @Test + public void testRedirectNoHost() { + Set redirectUris = new HashSet(Arrays.asList("scheme:/path")); + client.setRegisteredRedirectUri(redirectUris); + String requestedRedirect = "scheme:/path"; + assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestExactMatchRedirectResolver.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/ExactMatchRedirectResolverTests.java similarity index 76% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestExactMatchRedirectResolver.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/ExactMatchRedirectResolverTests.java index 7397b6d90..fc2d48ace 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/code/TestExactMatchRedirectResolver.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/ExactMatchRedirectResolverTests.java @@ -4,13 +4,13 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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.provider.code; +package org.springframework.security.oauth2.provider.endpoint; import static org.junit.Assert.assertEquals; @@ -20,15 +20,16 @@ import java.util.Set; import org.junit.Test; +import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException; -import org.springframework.security.oauth2.provider.BaseClientDetails; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.oauth2.provider.endpoint.ExactMatchRedirectResolver; /** * @author Dave Syer */ -public class TestExactMatchRedirectResolver { +public class ExactMatchRedirectResolverTests { private ExactMatchRedirectResolver resolver = new ExactMatchRedirectResolver(); private BaseClientDetails client = new BaseClientDetails(); @@ -39,23 +40,23 @@ public class TestExactMatchRedirectResolver { @Test ( expected = RedirectMismatchException.class ) public void testRedirectNotMatching() throws Exception { - Set redirectUris = new HashSet(Arrays.asList("http://anywhere.com/")); - String requestedRedirect = "http://anywhere.com/myendpoint"; + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/")); + String requestedRedirect = "https://anywhere.com/myendpoint"; client.setRegisteredRedirectUri(redirectUris); assertEquals(redirectUris.iterator().next(), resolver.resolveRedirect(requestedRedirect, client)); } - @Test + @Test(expected = InvalidRequestException.class) public void testRedirectWithNoRegisteredValue() throws Exception { - String requestedRedirect = "http://anywhere.com/myendpoint"; - assertEquals(requestedRedirect, resolver.resolveRedirect(requestedRedirect, client)); + String requestedRedirect = "https://anywhere.com/myendpoint"; + resolver.resolveRedirect(requestedRedirect, client); } // As we have one or more registered redirects, the redirect SHOULD be present. // If not we should expect a Oauth2Exception. @Test ( expected = OAuth2Exception.class ) public void testRedirectWithNoRequestedValue() throws Exception { - Set redirectUris = new HashSet(Arrays.asList("http://anywhere.com/", "http://nowhere.com/")); + Set redirectUris = new HashSet(Arrays.asList("https://anywhere.com/", "https://nowhere.com/")); client.setRegisteredRedirectUri(redirectUris); resolver.resolveRedirect(null, client); } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/FrameworkEndpointHandlerMappingTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/FrameworkEndpointHandlerMappingTests.java new file mode 100644 index 000000000..8f83ebc19 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/FrameworkEndpointHandlerMappingTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2013-2014 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.provider.endpoint; + +import static org.junit.Assert.assertEquals; + +import java.util.Collections; + +import org.junit.Test; + +/** + * @author Dave Syer + * + */ +public class FrameworkEndpointHandlerMappingTests { + + private FrameworkEndpointHandlerMapping mapping = new FrameworkEndpointHandlerMapping(); + + @Test + public void defaults() throws Exception { + assertEquals("/oauth/token", mapping.getPath("/oauth/token")); + assertEquals("/oauth/authorize", mapping.getPath("/oauth/authorize")); + assertEquals("/oauth/error", mapping.getPath("/oauth/error")); + assertEquals("/oauth/confirm_access", mapping.getPath("/oauth/confirm_access")); + } + + @Test + public void mappings() throws Exception { + mapping.setMappings(Collections.singletonMap("/oauth/token", "/token")); + assertEquals("/token", mapping.getPath("/oauth/token")); + } + + @Test + public void forward() throws Exception { + mapping.setMappings(Collections.singletonMap("/oauth/confirm_access", "forward:/approve")); + assertEquals("/approve", mapping.getPath("/oauth/confirm_access")); + } + + @Test + public void redirect() throws Exception { + mapping.setMappings(Collections.singletonMap("/oauth/confirm_access", "redirect:/approve")); + assertEquals("/approve", mapping.getPath("/oauth/confirm_access")); + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TestAuthorizationEndpoint.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TestAuthorizationEndpoint.java deleted file mode 100644 index b69f4f08e..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TestAuthorizationEndpoint.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright 2006-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package org.springframework.security.oauth2.provider.endpoint; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.springframework.security.oauth2.provider.AuthorizationRequest.REDIRECT_URI; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; -import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; -import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException; -import org.springframework.security.oauth2.provider.AuthorizationRequest; -import org.springframework.security.oauth2.provider.BaseClientDetails; -import org.springframework.security.oauth2.provider.ClientDetails; -import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; -import org.springframework.security.oauth2.provider.TokenGranter; -import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; -import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; -import org.springframework.security.oauth2.provider.code.AuthorizationRequestHolder; -import org.springframework.web.bind.support.SimpleSessionStatus; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.View; -import org.springframework.web.servlet.view.RedirectView; - -/** - * @author Dave Syer - * - */ -public class TestAuthorizationEndpoint { - - private AuthorizationEndpoint endpoint = new AuthorizationEndpoint(); - - private HashMap model = new HashMap(); - - private SimpleSessionStatus sessionStatus = new SimpleSessionStatus(); - - private UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken("foo", "bar", - Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))); - - private BaseClientDetails client; - - private DefaultAuthorizationRequest getAuthorizationRequest(String clientId, String redirectUri, String state, - String scope) { - HashMap parameters = new HashMap(); - parameters.put("client_id", clientId); - if (redirectUri != null) { - parameters.put("redirect_uri", redirectUri); - } - if (state != null) { - parameters.put("state", state); - } - if (scope != null) { - parameters.put("scope", scope); - } - return new DefaultAuthorizationRequest(parameters); - } - - @Before - public void init() throws Exception { - client = new BaseClientDetails(); - client.setRegisteredRedirectUri(Collections.singleton("http://anywhere.com/")); - client.setAuthorizedGrantTypes(Arrays.asList("authorization_code", "implicit")); - endpoint.setClientDetailsService(new ClientDetailsService() { - public ClientDetails loadClientByClientId(String clientId) throws OAuth2Exception { - return client; - } - }); - endpoint.setTokenGranter(new TokenGranter() { - public OAuth2AccessToken grant(String grantType, AuthorizationRequest authorizationRequest) { - return null; - } - }); - endpoint.setRedirectResolver(new DefaultRedirectResolver()); - endpoint.afterPropertiesSet(); - } - - @Test(expected = IllegalStateException.class) - public void testMandatoryProperties() throws Exception { - endpoint = new AuthorizationEndpoint(); - endpoint.afterPropertiesSet(); - } - - @Test - public void testStartAuthorizationCodeFlow() throws Exception { - ModelAndView result = endpoint.authorize(model, "code", getAuthorizationRequest("foo", null, null, null) - .getAuthorizationParameters(), sessionStatus, principal); - assertEquals("forward:/oauth/confirm_access", result.getViewName()); - } - - @Test(expected = OAuth2Exception.class) - public void testStartAuthorizationCodeFlowForClientCredentialsFails() throws Exception { - client.setAuthorizedGrantTypes(Collections.singleton("client_credentials")); - ModelAndView result = endpoint.authorize(model, "code", getAuthorizationRequest("foo", null, null, null) - .getAuthorizationParameters(), sessionStatus, principal); - assertEquals("forward:/oauth/confirm_access", result.getViewName()); - } - - @Test - public void testAuthorizationCodeWithFragment() throws Exception { - endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put("authorizationRequest", getAuthorizationRequest("foo", "http://anywhere.com/#bar", null, null)); - View result = endpoint.approveOrDeny( - Collections.singletonMap(AuthorizationRequest.USER_OAUTH_APPROVAL, "true"), model, sessionStatus, - principal); - assertEquals("http://anywhere.com/?code=thecode#bar", ((RedirectView) result).getUrl()); - } - - @Test - public void testAuthorizationCodeError() throws Exception { - endpoint.setUserApprovalHandler(new UserApprovalHandler() { - public AuthorizationRequest updateBeforeApproval(AuthorizationRequest authorizationRequest, - Authentication userAuthentication) { - return authorizationRequest; - } - - public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { - return true; - } - }); - endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices() { - @Override - public String createAuthorizationCode(AuthorizationRequestHolder authentication) { - throw new InvalidScopeException("FOO"); - } - }); - ModelAndView result = endpoint.authorize(model, "code", - getAuthorizationRequest("foo", "http://anywhere.com/", "mystate", "myscope") - .getAuthorizationParameters(), sessionStatus, principal); - String url = ((RedirectView) result.getView()).getUrl(); - assertTrue("Wrong view: " + result, url.startsWith("http://anywhere.com/")); - assertTrue("No error: " + result, url.contains("?error=")); - assertTrue("Wrong state: " + result, url.contains("&state=mystate")); - - } - - @Test - public void testAuthorizationCodeWithMultipleResponseTypes() throws Exception { - ModelAndView result = endpoint.authorize(model, "code other", getAuthorizationRequest("foo", null, null, null) - .getAuthorizationParameters(), sessionStatus, principal); - assertEquals("forward:/oauth/confirm_access", result.getViewName()); - } - - @Test - public void testImplicitPreApproved() throws Exception { - endpoint.setTokenGranter(new TokenGranter() { - public OAuth2AccessToken grant(String grantType, AuthorizationRequest authorizationRequest) { - return new DefaultOAuth2AccessToken("FOO"); - } - }); - endpoint.setUserApprovalHandler(new UserApprovalHandler() { - public AuthorizationRequest updateBeforeApproval(AuthorizationRequest authorizationRequest, - Authentication userAuthentication) { - return authorizationRequest; - } - - public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { - return true; - } - }); - AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "http://anywhere.com/", "mystate", - "myscope"); - ModelAndView result = endpoint.authorize(model, "token", authorizationRequest.getAuthorizationParameters(), - sessionStatus, principal); - String url = ((RedirectView) result.getView()).getUrl(); - assertTrue("Wrong view: " + result, url.startsWith("http://anywhere.com/")); - assertTrue("Wrong state: " + result, url.contains("&state=mystate")); - } - - @Test(expected = InvalidScopeException.class) - public void testImplicitPreApprovedButInvalid() throws Exception { - endpoint.setTokenGranter(new TokenGranter() { - public OAuth2AccessToken grant(String grantType, AuthorizationRequest authorizationRequest) { - throw new IllegalStateException("Shouldn't be called"); - } - }); - endpoint.setUserApprovalHandler(new UserApprovalHandler() { - public AuthorizationRequest updateBeforeApproval(AuthorizationRequest authorizationRequest, - Authentication userAuthentication) { - return authorizationRequest; - } - - public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { - return true; - } - }); - client.setScope(Collections.singleton("smallscope")); - AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "http://anywhere.com/", "mystate", - "bigscope"); - ModelAndView result = endpoint.authorize(model, "token", authorizationRequest.getAuthorizationParameters(), - sessionStatus, principal); - String url = ((RedirectView) result.getView()).getUrl(); - assertTrue("Wrong view: " + result, url.startsWith("http://anywhere.com/")); - } - - @Test - public void testImplicitUnapproved() throws Exception { - endpoint.setTokenGranter(new TokenGranter() { - public OAuth2AccessToken grant(String grantType, AuthorizationRequest authorizationRequest) { - return null; - } - }); - AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "http://anywhere.com/", "mystate", - "myscope"); - ModelAndView result = endpoint.authorize(model, "token", authorizationRequest.getAuthorizationParameters(), - sessionStatus, principal); - assertEquals("forward:/oauth/confirm_access", result.getViewName()); - } - - @Test - public void testImplicitError() throws Exception { - endpoint.setUserApprovalHandler(new UserApprovalHandler() { - public AuthorizationRequest updateBeforeApproval(AuthorizationRequest authorizationRequest, - Authentication userAuthentication) { - return authorizationRequest; - } - - public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { - return true; - } - }); - endpoint.setTokenGranter(new TokenGranter() { - public OAuth2AccessToken grant(String grantType, AuthorizationRequest authorizationRequest) { - return null; - } - }); - AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "http://anywhere.com/", "mystate", - "myscope"); - ModelAndView result = endpoint.authorize(model, "token", authorizationRequest.getAuthorizationParameters(), - sessionStatus, principal); - - String url = ((RedirectView) result.getView()).getUrl(); - assertTrue("Wrong view: " + result, url.startsWith("http://anywhere.com/")); - assertTrue("No error: " + result, url.contains("#error=")); - assertTrue("Wrong state: " + result, url.contains("&state=mystate")); - - } - - @Test - public void testApproveOrDeny() throws Exception { - DefaultAuthorizationRequest request = getAuthorizationRequest("foo", "http://anywhere.com/", null, null); - request.setApproved(true); - model.put("authorizationRequest", request); - View result = endpoint.approveOrDeny(null, model, sessionStatus, principal); - assertTrue("Wrong view: " + result, ((RedirectView) result).getUrl().startsWith("http://anywhere.com/")); - } - - @Test - public void testApprovalDenied() throws Exception { - model.put("authorizationRequest", getAuthorizationRequest("foo", "http://anywhere.com/", null, null)); - View result = endpoint.approveOrDeny(null, model, sessionStatus, principal); - String url = ((RedirectView) result).getUrl(); - assertTrue("Wrong view: " + result, url.startsWith("http://anywhere.com/")); - assertTrue("Wrong view: " + result, url.contains("error=access_denied")); - } - - @Test - public void testDirectApproval() throws Exception { - ModelAndView result = endpoint.authorize(model, "code", - getAuthorizationRequest("foo", "http://anywhere.com/", null, null).getAuthorizationParameters(), - sessionStatus, principal); - // Should go to approval page (SECOAUTH-191) - assertFalse(result.getView() instanceof RedirectView); - } - - @Test - public void testRedirectUriOptionalForAuthorization() throws Exception { - ModelAndView result = endpoint.authorize(model, "code", getAuthorizationRequest("foo", null, null, null) - .getAuthorizationParameters(), sessionStatus, principal); - // RedirectUri parameter should be null (SECOAUTH-333), however the resolvedRedirectUri not - DefaultAuthorizationRequest authorizationRequest = (DefaultAuthorizationRequest) result.getModelMap().get( - "authorizationRequest"); - assertNull(authorizationRequest.getAuthorizationParameters().get(REDIRECT_URI)); - assertEquals("http://anywhere.com/", authorizationRequest.getRedirectUri()); - } - - @Test - public void testApproveOrDenyWithAuthorizationRequestWithoutRedirectUri() throws Exception { - DefaultAuthorizationRequest request = getAuthorizationRequest("foo", null, null, null); - request.setApproved(true); - model.put("authorizationRequest", request); - View result = endpoint.approveOrDeny(null, model, sessionStatus, principal); - assertTrue("Redirect view with code: " + result, - ((RedirectView) result).getUrl().startsWith("http://anywhere.com/?code=")); - } - - @Test - public void testAuthorizeWithNoRedirectUri() { - client.setRegisteredRedirectUri(Collections. emptySet()); - DefaultAuthorizationRequest request = getAuthorizationRequest("foo", null, null, null); - endpoint.setRedirectResolver(new RedirectResolver() { - public String resolveRedirect(String requestedRedirect, ClientDetails client) throws OAuth2Exception { - return null; - } - }); - try { - model.put("authorizationRequest", request); - endpoint.approveOrDeny(null, model, sessionStatus, principal); - fail("Contract of RedirectResolver is to return not-null redirecturi"); - } - catch (RedirectMismatchException e) { - } - - } - - private class StubAuthorizationCodeServices implements AuthorizationCodeServices { - private AuthorizationRequestHolder authentication; - - public String createAuthorizationCode(AuthorizationRequestHolder authentication) { - this.authentication = authentication; - return "thecode"; - } - - public AuthorizationRequestHolder consumeAuthorizationCode(String code) throws InvalidGrantException { - return authentication; - } - } - -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TestTokenEndpoint.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TestTokenEndpoint.java deleted file mode 100644 index 792ce4611..000000000 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TestTokenEndpoint.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2002-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.provider.endpoint; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.when; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.runners.MockitoJUnitRunner; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.provider.AuthorizationRequest; -import org.springframework.security.oauth2.provider.AuthorizationRequestManager; -import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; -import org.springframework.security.oauth2.provider.TokenGranter; - -/** - * @author Dave Syer - * @author Rob Winch - */ -@RunWith(MockitoJUnitRunner.class) -public class TestTokenEndpoint { - - @Mock - private TokenGranter tokenGranter; - - @Mock - private AuthorizationRequestManager authorizationRequestFactory; - - @Mock - private ClientDetailsService clientDetailsService; - - @Test - public void testGetAccessTokenWithNoClientId() { - - TokenEndpoint endpoint = new TokenEndpoint(); - endpoint.setTokenGranter(tokenGranter); - endpoint.setAuthorizationRequestManager(authorizationRequestFactory); - endpoint.setClientDetailsService(clientDetailsService); - - HashMap parameters = new HashMap(); - - OAuth2AccessToken expectedToken = new DefaultOAuth2AccessToken("FOO"); - when(tokenGranter.grant(Mockito.eq("authorization_code"), Mockito.any(AuthorizationRequest.class))).thenReturn( - expectedToken); - @SuppressWarnings("unchecked") - Map anyMap = Mockito.any(Map.class); - when(authorizationRequestFactory.createAuthorizationRequest(anyMap)).thenReturn( - new DefaultAuthorizationRequest(parameters)); - - ResponseEntity response = endpoint.getAccessToken(new UsernamePasswordAuthenticationToken( - null, null, Collections.singleton(new SimpleGrantedAuthority("ROLE_CLIENT"))), "authorization_code", - parameters); - - assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatusCode()); - OAuth2AccessToken body = response.getBody(); - assertEquals(body, expectedToken); - assertTrue("Wrong body: " + body, body.getTokenType() != null); - } - - @Test - public void testGetAccessTokenWithScope() { - - TokenEndpoint endpoint = new TokenEndpoint(); - endpoint.setTokenGranter(tokenGranter); - endpoint.setAuthorizationRequestManager(authorizationRequestFactory); - endpoint.setClientDetailsService(clientDetailsService); - - HashMap parameters = new HashMap(); - parameters.put("scope", "read"); - parameters.put("grant_type", "authorization_code"); - parameters.put("code", "kJAHDFG"); - - OAuth2AccessToken expectedToken = new DefaultOAuth2AccessToken("FOO"); - ArgumentCaptor captor = ArgumentCaptor.forClass(AuthorizationRequest.class); - when(tokenGranter.grant(Mockito.eq("authorization_code"), captor.capture())).thenReturn(expectedToken); - @SuppressWarnings("unchecked") - Map anyMap = Mockito.any(Map.class); - when(authorizationRequestFactory.createAuthorizationRequest(anyMap)).thenReturn( - new DefaultAuthorizationRequest(parameters)); - - ResponseEntity response = endpoint.getAccessToken(new UsernamePasswordAuthenticationToken( - null, null, Collections.singleton(new SimpleGrantedAuthority("ROLE_CLIENT"))), "authorization_code", - parameters); - - assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatusCode()); - OAuth2AccessToken body = response.getBody(); - assertEquals(body, expectedToken); - assertTrue("Wrong body: " + body, body.getTokenType() != null); - assertTrue("Scope of token request not cleared", captor.getValue().getScope().isEmpty()); - } - -} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TokenEndpointAuthenticationFilterTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TokenEndpointAuthenticationFilterTests.java new file mode 100644 index 000000000..154dbb5b8 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TokenEndpointAuthenticationFilterTests.java @@ -0,0 +1,117 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.provider.endpoint; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; + +/** + * @author Dave Syer + * + */ +public class TokenEndpointAuthenticationFilterTests { + + private MockHttpServletRequest request = new MockHttpServletRequest(); + + private MockHttpServletResponse response = new MockHttpServletResponse(); + + private MockFilterChain chain = new MockFilterChain(); + + private AuthenticationManager authenticationManager = Mockito.mock(AuthenticationManager.class); + + private BaseClientDetails client = new BaseClientDetails("foo", "resource", "scope", "authorization_code", + "ROLE_CLIENT"); + + private ClientDetailsService clientDetailsService = new ClientDetailsService() { + public ClientDetails loadClientByClientId(String clientId) throws OAuth2Exception { + return client; + } + }; + + private OAuth2RequestFactory oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetailsService); + + @Before + public void init() { + SecurityContextHolder.clearContext(); + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken("client", "secret", AuthorityUtils + .commaSeparatedStringToAuthorityList("ROLE_CLIENT"))); + } + + @After + public void close() { + SecurityContextHolder.clearContext(); + } + + @Test + public void testPasswordGrant() throws Exception { + request.setParameter("grant_type", "password"); + request.setParameter("client_id", "foo"); + Mockito.when(authenticationManager.authenticate(Mockito. any())).thenReturn( + new UsernamePasswordAuthenticationToken("foo", "bar", AuthorityUtils + .commaSeparatedStringToAuthorityList("ROLE_USER"))); + TokenEndpointAuthenticationFilter filter = new TokenEndpointAuthenticationFilter(authenticationManager, oAuth2RequestFactory); + filter.doFilter(request, response, chain); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + assertTrue(authentication instanceof OAuth2Authentication); + assertTrue(authentication.isAuthenticated()); + } + + @Test + public void testPasswordGrantWithUnAuthenticatedClient() throws Exception { + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken("client", "secret")); + request.setParameter("grant_type", "password"); + Mockito.when(authenticationManager.authenticate(Mockito. any())).thenReturn( + new UsernamePasswordAuthenticationToken("foo", "bar", AuthorityUtils + .commaSeparatedStringToAuthorityList("ROLE_USER"))); + TokenEndpointAuthenticationFilter filter = new TokenEndpointAuthenticationFilter(authenticationManager, oAuth2RequestFactory); + filter.doFilter(request, response, chain); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + assertTrue(authentication instanceof OAuth2Authentication); + assertFalse(authentication.isAuthenticated()); + } + + @Test + public void testNoGrantType() throws Exception { + TokenEndpointAuthenticationFilter filter = new TokenEndpointAuthenticationFilter(authenticationManager, oAuth2RequestFactory); + filter.doFilter(request, response, chain); + // Just the client + assertTrue(SecurityContextHolder.getContext().getAuthentication() instanceof UsernamePasswordAuthenticationToken); + } + +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TokenEndpointTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TokenEndpointTests.java new file mode 100644 index 000000000..5b5099b63 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TokenEndpointTests.java @@ -0,0 +1,258 @@ +/* + * Copyright 2002-2011 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.provider.endpoint; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + +import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.TokenGranter; +import org.springframework.security.oauth2.provider.TokenRequest; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.web.HttpRequestMethodNotSupportedException; + +/** + * @author Dave Syer + * @author Rob Winch + */ +@RunWith(MockitoJUnitRunner.class) +public class TokenEndpointTests { + + @Mock + private TokenGranter tokenGranter; + + @Mock + private OAuth2RequestFactory authorizationRequestFactory; + + @Mock + private ClientDetailsService clientDetailsService; + + private String clientId = "client"; + private BaseClientDetails clientDetails = new BaseClientDetails(); + + private TokenEndpoint endpoint; + + private Principal clientAuthentication = new UsernamePasswordAuthenticationToken("client", null, + Collections.singleton(new SimpleGrantedAuthority("ROLE_CLIENT"))); + + private TokenRequest createFromParameters(Map parameters) { + TokenRequest request = new TokenRequest(parameters, parameters.get(OAuth2Utils.CLIENT_ID), + OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)), + parameters.get(OAuth2Utils.GRANT_TYPE)); + return request; + } + + @Before + public void init() { + endpoint = new TokenEndpoint(); + endpoint.setTokenGranter(tokenGranter); + endpoint.setOAuth2RequestFactory(authorizationRequestFactory); + endpoint.setClientDetailsService(clientDetailsService); + clientDetails.setClientId(clientId); + } + + @Test + public void testGetAccessTokenWithNoClientId() throws HttpRequestMethodNotSupportedException { + + HashMap parameters = new HashMap(); + parameters.put(OAuth2Utils.GRANT_TYPE, "authorization_code"); + + OAuth2AccessToken expectedToken = new DefaultOAuth2AccessToken("FOO"); + when(tokenGranter.grant(eq("authorization_code"), Mockito.any(TokenRequest.class))).thenReturn( + expectedToken); + @SuppressWarnings("unchecked") + Map anyMap = Mockito.any(Map.class); + when(authorizationRequestFactory.createTokenRequest(anyMap, Mockito.any(ClientDetails.class))).thenReturn( + createFromParameters(parameters)); + + clientAuthentication = new UsernamePasswordAuthenticationToken(null, null, + Collections.singleton(new SimpleGrantedAuthority("ROLE_CLIENT"))); + ResponseEntity response = endpoint.postAccessToken(clientAuthentication, parameters); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + OAuth2AccessToken body = response.getBody(); + assertEquals(body, expectedToken); + assertTrue("Wrong body: " + body, body.getTokenType() != null); + } + + @Test + public void testGetAccessTokenWithScope() throws HttpRequestMethodNotSupportedException { + + when(clientDetailsService.loadClientByClientId(clientId)).thenReturn(clientDetails); + + HashMap parameters = new HashMap(); + parameters.put("client_id", clientId); + parameters.put("scope", "read"); + parameters.put("grant_type", "authorization_code"); + parameters.put("code", "kJAHDFG"); + + OAuth2AccessToken expectedToken = new DefaultOAuth2AccessToken("FOO"); + ArgumentCaptor captor = ArgumentCaptor.forClass(TokenRequest.class); + + when(tokenGranter.grant(eq("authorization_code"), captor.capture())).thenReturn(expectedToken); + @SuppressWarnings("unchecked") + Map anyMap = Mockito.any(Map.class); + when(authorizationRequestFactory.createTokenRequest(anyMap, eq(clientDetails))).thenReturn( + createFromParameters(parameters)); + + ResponseEntity response = endpoint.postAccessToken(clientAuthentication, parameters); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + OAuth2AccessToken body = response.getBody(); + assertEquals(body, expectedToken); + assertTrue("Wrong body: " + body, body.getTokenType() != null); + assertTrue("Scope of token request not cleared", captor.getValue().getScope().isEmpty()); + } + + @Test(expected = HttpRequestMethodNotSupportedException.class) + public void testGetAccessTokenWithUnsupportedRequestParameters() throws HttpRequestMethodNotSupportedException { + endpoint.getAccessToken(clientAuthentication, new HashMap()); + } + + @Test + public void testGetAccessTokenWithSupportedRequestParametersNotPost() throws HttpRequestMethodNotSupportedException { + endpoint.setAllowedRequestMethods(new HashSet(Arrays.asList(HttpMethod.GET))); + HashMap parameters = new HashMap(); + parameters.put("client_id", clientId); + parameters.put("scope", "read"); + parameters.put("grant_type", "authorization_code"); + parameters.put("code", "kJAHDFG"); + + OAuth2AccessToken expectedToken = new DefaultOAuth2AccessToken("FOO"); + when(tokenGranter.grant(eq("authorization_code"), Mockito.any(TokenRequest.class))).thenReturn( + expectedToken); + @SuppressWarnings("unchecked") + Map anyMap = Mockito.any(Map.class); + when(authorizationRequestFactory.createTokenRequest(anyMap, Mockito.any(ClientDetails.class))).thenReturn( + createFromParameters(parameters)); + + ResponseEntity response = endpoint.getAccessToken(clientAuthentication, parameters); + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + OAuth2AccessToken body = response.getBody(); + assertEquals(body, expectedToken); + assertTrue("Wrong body: " + body, body.getTokenType() != null); + } + + @Test(expected = InvalidGrantException.class) + public void testImplicitGrant() throws HttpRequestMethodNotSupportedException { + HashMap parameters = new HashMap(); + parameters.put(OAuth2Utils.GRANT_TYPE, "implicit"); + parameters.put("client_id", clientId); + parameters.put("scope", "read"); + @SuppressWarnings("unchecked") + Map anyMap = Mockito.any(Map.class); + when(authorizationRequestFactory.createTokenRequest(anyMap, eq(clientDetails))).thenReturn( + createFromParameters(parameters)); + when(clientDetailsService.loadClientByClientId(clientId)).thenReturn(clientDetails); + endpoint.postAccessToken(clientAuthentication, parameters); + } + + // gh-1268 + @Test + public void testGetAccessTokenReturnsHeaderContentTypeJson() throws Exception { + when(clientDetailsService.loadClientByClientId(clientId)).thenReturn(clientDetails); + + HashMap parameters = new HashMap(); + parameters.put("client_id", clientId); + parameters.put("scope", "read"); + parameters.put("grant_type", "authorization_code"); + parameters.put("code", "kJAHDFG"); + + OAuth2AccessToken expectedToken = new DefaultOAuth2AccessToken("FOO"); + + when(tokenGranter.grant(eq("authorization_code"), any(TokenRequest.class))).thenReturn(expectedToken); + + when(authorizationRequestFactory.createTokenRequest(any(Map.class), eq(clientDetails))).thenReturn( + createFromParameters(parameters)); + + ResponseEntity response = endpoint.postAccessToken(clientAuthentication, parameters); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("application/json;charset=UTF-8", response.getHeaders().get("Content-Type").iterator().next()); + } + + @Test(expected = InvalidRequestException.class) + public void testRefreshTokenGrantTypeWithoutRefreshTokenParameter() throws Exception { + when(clientDetailsService.loadClientByClientId(clientId)).thenReturn(clientDetails); + + HashMap parameters = new HashMap(); + parameters.put("client_id", clientId); + parameters.put("scope", "read"); + parameters.put("grant_type", "refresh_token"); + + when(authorizationRequestFactory.createTokenRequest(any(Map.class), eq(clientDetails))).thenReturn( + createFromParameters(parameters)); + + endpoint.postAccessToken(clientAuthentication, parameters); + } + + @Test + public void testGetAccessTokenWithRefreshToken() throws Exception { + when(clientDetailsService.loadClientByClientId(clientId)).thenReturn(clientDetails); + + HashMap parameters = new HashMap(); + parameters.put("client_id", clientId); + parameters.put("scope", "read"); + parameters.put("grant_type", "refresh_token"); + parameters.put("refresh_token", "kJAHDFG"); + + OAuth2AccessToken expectedToken = new DefaultOAuth2AccessToken("FOO"); + + when(tokenGranter.grant(eq("refresh_token"), any(TokenRequest.class))).thenReturn(expectedToken); + + when(authorizationRequestFactory.createTokenRequest(any(Map.class), eq(clientDetails))).thenReturn( + createFromParameters(parameters)); + + ResponseEntity response = endpoint.postAccessToken(clientAuthentication, parameters); + + assertEquals(expectedToken, response.getBody()); + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/WhitelabelApprovalEndpointTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/WhitelabelApprovalEndpointTests.java new file mode 100644 index 000000000..f4c408dc7 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/WhitelabelApprovalEndpointTests.java @@ -0,0 +1,157 @@ +/* + * Copyright 2006-2011 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.provider.endpoint; + +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.web.csrf.DefaultCsrfToken; +import org.springframework.web.servlet.ModelAndView; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertTrue; + +/** + * @author Dave Syer + * + */ +public class WhitelabelApprovalEndpointTests { + + private WhitelabelApprovalEndpoint endpoint = new WhitelabelApprovalEndpoint(); + private Map parameters = new HashMap(); + private MockHttpServletRequest request = new MockHttpServletRequest(); + private MockHttpServletResponse response = new MockHttpServletResponse(); + + private AuthorizationRequest createFromParameters(Map authorizationParameters) { + AuthorizationRequest request = new AuthorizationRequest(authorizationParameters, Collections. emptyMap(), + authorizationParameters.get(OAuth2Utils.CLIENT_ID), + OAuth2Utils.parseParameterList(authorizationParameters.get(OAuth2Utils.SCOPE)), null, + null, false, authorizationParameters.get(OAuth2Utils.STATE), + authorizationParameters.get(OAuth2Utils.REDIRECT_URI), + OAuth2Utils.parseParameterList(authorizationParameters.get(OAuth2Utils.RESPONSE_TYPE))); + return request; + } + + @Test + public void testApprovalPage() throws Exception { + request.setContextPath("/foo"); + parameters.put("client_id", "client"); + HashMap model = new HashMap(); + model.put("authorizationRequest", createFromParameters(parameters)); + ModelAndView result = endpoint.getAccessConfirmation(model, request); + result.getView().render(result.getModel(), request , response); + String content = response.getContentAsString(); + assertTrue("Wrong content: " + content, content.contains(" model = new HashMap(); + model.put("authorizationRequest", createFromParameters(parameters)); + model.put("scopes", Collections.singletonMap("scope.read", "true")); + ModelAndView result = endpoint.getAccessConfirmation(model, request); + result.getView().render(result.getModel(), request , response); + String content = response.getContentAsString(); + assertTrue("Wrong content: " + content, content.contains("scope.read")); + assertTrue("Wrong content: " + content, content.contains("checked")); + assertTrue("Wrong content: " + content, content.contains("/foo/oauth/authorize")); + assertTrue("Wrong content: " + content, !content.contains("${")); + assertTrue("Wrong content: " + content, !content.contains("_csrf")); + assertTrue("Wrong content: " + content, !content.contains("%")); + } + + @Test + public void testApprovalPageWithCsrf() throws Exception { + request.setContextPath("/foo"); + request.setAttribute("_csrf", new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "FOO")); + parameters.put("client_id", "client"); + HashMap model = new HashMap(); + model.put("authorizationRequest", createFromParameters(parameters)); + ModelAndView result = endpoint.getAccessConfirmation(model, request); + result.getView().render(result.getModel(), request , response); + String content = response.getContentAsString(); + assertTrue("Wrong content: " + content, content.contains("_csrf")); + assertTrue("Wrong content: " + content, content.contains("/foo/oauth/authorize")); + assertTrue("Wrong content: " + content, !content.contains("${")); + } + + // gh-1340 + @Test + public void testApprovalPageWithSuspectScope() throws Exception { + request.setContextPath("/foo"); + parameters.put("client_id", "client"); + HashMap model = new HashMap(); + model.put("authorizationRequest", createFromParameters(parameters)); + String scope = "${T(java.lang.Runtime).getRuntime().exec(\"cd ..\")}"; + String escapedScope = "T(java.lang.Runtime).getRuntime().exec("cd ..")"; + model.put("scopes", Collections.singletonMap(scope, "true")); + ModelAndView result = endpoint.getAccessConfirmation(model, request); + result.getView().render(result.getModel(), request , response); + String content = response.getContentAsString(); + assertTrue("Wrong content: " + content, !content.contains(scope)); + assertTrue("Wrong content: " + content, content.contains(escapedScope)); + } + + @Test + public void testApprovalPageWithScopesInForm() throws Exception { + String expectedContent = "

OAuth Approval

Do you authorize \"client\" to access your protected resources?

" + + "
" + + "
    " + + "
  • scope.read: Approve " + + "Deny
"; + request.setContextPath("/foo"); + request.setAttribute("_csrf", new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "FOO")); + parameters.put("client_id", "client"); + HashMap model = new HashMap(); + model.put("authorizationRequest", createFromParameters(parameters)); + model.put("scopes", Collections.singletonMap("scope.read", "true")); + ModelAndView result = endpoint.getAccessConfirmation(model, request); + result.getView().render(result.getModel(), request , response); + String content = response.getContentAsString(); + assertTrue("Wrong content: " + content, content.equals(expectedContent)); + } + + @Test + public void testApprovalPageWithoutScopesInForm() throws Exception { + String expectedContent = "

OAuth Approval

Do you authorize \"client\" to access your protected resources?

" + + "
" + + "
" + + "
" + + "
"; + request.setContextPath("/foo"); + request.setAttribute("_csrf", new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "FOO")); + parameters.put("client_id", "client"); + HashMap model = new HashMap(); + model.put("authorizationRequest", createFromParameters(parameters)); + ModelAndView result = endpoint.getAccessConfirmation(model, request); + result.getView().render(result.getModel(), request , response); + String content = response.getContentAsString(); + assertTrue("Wrong content: " + content, content.equals(expectedContent)); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TestWhitelabelApprovalEndpoint.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/WhitelabelErrorEndpointTests.java similarity index 64% rename from spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TestWhitelabelApprovalEndpoint.java rename to spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/WhitelabelErrorEndpointTests.java index b7e56b57d..f97741a20 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/TestWhitelabelApprovalEndpoint.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/WhitelabelErrorEndpointTests.java @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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 @@ -14,52 +14,52 @@ package org.springframework.security.oauth2.provider.endpoint; -import static org.junit.Assert.assertTrue; - -import java.util.HashMap; -import java.util.Map; +import static org.junit.Assert.*; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.oauth2.common.exceptions.InvalidClientException; -import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.web.servlet.ModelAndView; /** * @author Dave Syer * */ -public class TestWhitelabelApprovalEndpoint { +public class WhitelabelErrorEndpointTests { - private WhitelabelApprovalEndpoint endpoint = new WhitelabelApprovalEndpoint(); - private Map parameters = new HashMap(); + private WhitelabelErrorEndpoint endpoint = new WhitelabelErrorEndpoint(); private MockHttpServletRequest request = new MockHttpServletRequest(); private MockHttpServletResponse response = new MockHttpServletResponse(); @Test - public void testApprovalPage() throws Exception { + public void testErrorPage() throws Exception { request.setContextPath("/foo"); - parameters.put("client_id", "client"); - HashMap model = new HashMap(); - model.put("authorizationRequest",new DefaultAuthorizationRequest(parameters)); - ModelAndView result = endpoint.getAccessConfirmation(model); + request.setAttribute("error", new InvalidClientException("FOO")); + ModelAndView result = endpoint.handleError(request); result.getView().render(result.getModel(), request , response); String content = response.getContentAsString(); - assertTrue("Wrong content: " + content, content.contains("alert('XSS');")); + ModelAndView result = endpoint.handleError(request); + result.getView().render(result.getModel(), request, response); + String content = response.getContentAsString(); + assertFalse("Wrong content : " + content, content.contains("