Privilege Escalation in spring-security-oauth2
Description
Spring Security OAuth Authorization Server with a custom approval endpoint is vulnerable to privilege escalation via a crafted request that modifies the saved authorization request.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Spring Security OAuth Authorization Server with a custom approval endpoint is vulnerable to privilege escalation via a crafted request that modifies the saved authorization request.
Vulnerability
A privilege escalation vulnerability exists in Spring Security OAuth versions 2.3 prior to 2.3.4, 2.2 prior to 2.2.3, 2.1 prior to 2.1.3, 2.0 prior to 2.0.16, and older unsupported versions. The issue occurs when an application acts as an Authorization Server (e.g., @EnableAuthorizationServer) and uses a custom approval endpoint that declares AuthorizationRequest as a controller method argument. A malicious user or attacker can craft a request to the approval endpoint that can modify the previously saved authorization request, leading to privilege escalation on the subsequent approval. The vulnerability does not affect applications using the default approval endpoint or those acting only as a Resource Server or Client [1][2][3][4].
Exploitation
An attacker needs network access to the Authorization Server's approval endpoint and the ability to craft a malicious HTTP request. The attack involves sending a specially crafted request to the custom approval endpoint that includes parameters that alter the stored AuthorizationRequest object. This can be done without prior authentication if the endpoint is publicly accessible, or after obtaining a valid authorization request in the OAuth flow. The exact sequence: the attacker obtains a valid authorization request (e.g., by initiating an OAuth flow), then sends a modified request to the approval endpoint that changes the scope, client ID, or other attributes of the saved request, thereby escalating their privileges upon approval [1][2][3][4].
Impact
On successful exploitation, an attacker can achieve privilege escalation within the OAuth authorization flow. This could allow the attacker to gain access to resources or scopes that they should not be authorized for, potentially leading to information disclosure, unauthorized actions, or further compromise of the OAuth-protected resources. The impact is limited to applications that meet the specific configuration criteria; default configurations are not affected [1][2][3][4].
Mitigation
Users should upgrade to patched versions: 2.3.4, 2.2.3, 2.1.3, or 2.0.16. The fix involves validating the authorization request on approval, as implemented in commits 4082ec7, f92223a, 6237766, and ddd65cd [1][2][3][4]. For users unable to upgrade immediately, a workaround is to avoid using custom approval endpoints that declare AuthorizationRequest as a controller method argument, or to implement additional validation of the authorization request parameters manually. No known exploitation in the wild has been reported at the time of disclosure.
- Validate authorization request on approval · spring-attic/spring-security-oauth@4082ec7
- Validate authorization request on approval · spring-attic/spring-security-oauth@f92223a
- Validate authorization request on approval · spring-attic/spring-security-oauth@6237766
- Validate authorization request on approval · spring-attic/spring-security-oauth@ddd65cd
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.springframework.security.oauth:spring-security-oauth2Maven | >= 2.0.0, < 2.0.16 | 2.0.16 |
org.springframework.security.oauth:spring-security-oauth2Maven | >= 2.1.0, < 2.1.3 | 2.1.3 |
org.springframework.security.oauth:spring-security-oauth2Maven | >= 2.2.0, < 2.2.3.RELEASE | 2.2.3.RELEASE |
org.springframework.security.oauth:spring-security-oauth2Maven | >= 2.3.0, < 2.3.4.RELEASE | 2.3.4.RELEASE |
Affected products
2- ghsa-coordsRange: >= 2.0.0, < 2.0.16
- Pivotal/Spring Security OAuthv5Range: 2.3
Patches
8b31d4d949f4eRelease version 2.0.16.RELEASE
34 files changed · +36 −36
pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <name>OAuth for Spring Security</name> <description>Parent Project for OAuth Support for Spring Security</description> <packaging>pom</packaging> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> <url>http://static.springframework.org/spring-security/oauth</url> <modules>
samples/oauth2/sparklr/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/oauth2/tonr/pom.xml+1 −1 modified@@ -6,7 +6,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/oauth/sparklr/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/oauth/tonr/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/pom.xml+1 −1 modified@@ -4,7 +4,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <artifactId>spring-security-oauth-samples</artifactId>
spring-security-oauth2/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <artifactId>spring-security-oauth2</artifactId>
spring-security-oauth/pom.xml+1 −1 modified@@ -4,7 +4,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <artifactId>spring-security-oauth</artifactId>
tests/annotation/approval/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/annotation/client/pom.xml+1 −1 modified@@ -11,7 +11,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <properties>
tests/annotation/common/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/annotation/custom-authentication/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/annotation/custom-grant/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/annotation/form/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/annotation/jaxb/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/annotation/jdbc/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/annotation/jpa/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/annotation/jwt/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/annotation/mappings/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/annotation/multi/pom.xml+1 −1 modified@@ -9,7 +9,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/annotation/pom.xml+2 −2 modified@@ -4,7 +4,7 @@ <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> <packaging>pom</packaging> <modules> @@ -39,7 +39,7 @@ <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> <exclusions> <exclusion> <artifactId>jackson-mapper-asl</artifactId>
tests/annotation/resource/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/annotation/ssl/pom.xml+1 −1 modified@@ -11,7 +11,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/annotation/vanilla/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/pom.xml+1 −1 modified@@ -4,7 +4,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <artifactId>spring-security-oauth-tests</artifactId>
tests/xml/approval/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/xml/client/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/xml/common/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/xml/form/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/xml/jdbc/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/xml/jwt/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/xml/mappings/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
tests/xml/pom.xml+2 −2 modified@@ -4,7 +4,7 @@ <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> <packaging>pom</packaging> <modules> @@ -33,7 +33,7 @@ <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> <exclusions> <exclusion> <artifactId>jackson-mapper-asl</artifactId>
tests/xml/vanilla/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.0.16.BUILD-SNAPSHOT</version> + <version>2.0.16.RELEASE</version> </parent> <dependencies>
818957ec6e6fRelease version 2.1.3.RELEASE
34 files changed · +36 −36
pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <name>OAuth for Spring Security</name> <description>Parent Project for OAuth Support for Spring Security</description> <packaging>pom</packaging> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> <url>http://static.springframework.org/spring-security/oauth</url> <modules>
samples/oauth2/sparklr/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/oauth2/tonr/pom.xml+1 −1 modified@@ -6,7 +6,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/oauth/sparklr/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/oauth/tonr/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/pom.xml+1 −1 modified@@ -4,7 +4,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <artifactId>spring-security-oauth-samples</artifactId>
spring-security-oauth2/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <artifactId>spring-security-oauth2</artifactId>
spring-security-oauth/pom.xml+1 −1 modified@@ -4,7 +4,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <artifactId>spring-security-oauth</artifactId>
tests/annotation/approval/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/annotation/client/pom.xml+1 −1 modified@@ -11,7 +11,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <properties>
tests/annotation/common/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/annotation/custom-authentication/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/annotation/custom-grant/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/annotation/form/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/annotation/jaxb/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/annotation/jdbc/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/annotation/jpa/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/annotation/jwt/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/annotation/mappings/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/annotation/multi/pom.xml+1 −1 modified@@ -9,7 +9,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/annotation/pom.xml+2 −2 modified@@ -4,7 +4,7 @@ <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> <packaging>pom</packaging> <modules> @@ -39,7 +39,7 @@ <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> <exclusions> <exclusion> <artifactId>jackson-mapper-asl</artifactId>
tests/annotation/resource/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/annotation/ssl/pom.xml+1 −1 modified@@ -11,7 +11,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/annotation/vanilla/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/pom.xml+1 −1 modified@@ -4,7 +4,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <artifactId>spring-security-oauth-tests</artifactId>
tests/xml/approval/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/xml/client/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/xml/common/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/xml/form/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/xml/jdbc/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/xml/jwt/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/xml/mappings/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
tests/xml/pom.xml+2 −2 modified@@ -4,7 +4,7 @@ <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> <packaging>pom</packaging> <modules> @@ -33,7 +33,7 @@ <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> <exclusions> <exclusion> <artifactId>jackson-mapper-asl</artifactId>
tests/xml/vanilla/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.1.3.BUILD-SNAPSHOT</version> + <version>2.1.3.RELEASE</version> </parent> <dependencies>
636e2e01fa32Release version 2.2.3.RELEASE
34 files changed · +36 −36
pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <name>OAuth for Spring Security</name> <description>Parent Project for OAuth Support for Spring Security</description> <packaging>pom</packaging> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> <url>http://static.springframework.org/spring-security/oauth</url> <modules>
samples/oauth2/sparklr/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/oauth2/tonr/pom.xml+1 −1 modified@@ -6,7 +6,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/oauth/sparklr/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/oauth/tonr/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/pom.xml+1 −1 modified@@ -4,7 +4,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <artifactId>spring-security-oauth-samples</artifactId>
spring-security-oauth2/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <artifactId>spring-security-oauth2</artifactId>
spring-security-oauth/pom.xml+1 −1 modified@@ -4,7 +4,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <artifactId>spring-security-oauth</artifactId>
tests/annotation/approval/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/annotation/client/pom.xml+1 −1 modified@@ -11,7 +11,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <properties>
tests/annotation/common/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/annotation/custom-authentication/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/annotation/custom-grant/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/annotation/form/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/annotation/jaxb/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/annotation/jdbc/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/annotation/jpa/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/annotation/jwt/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/annotation/mappings/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/annotation/multi/pom.xml+1 −1 modified@@ -9,7 +9,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/annotation/pom.xml+2 −2 modified@@ -4,7 +4,7 @@ <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> <packaging>pom</packaging> <modules> @@ -39,7 +39,7 @@ <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> <exclusions> <exclusion> <artifactId>jackson-mapper-asl</artifactId>
tests/annotation/resource/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/annotation/ssl/pom.xml+1 −1 modified@@ -11,7 +11,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/annotation/vanilla/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/pom.xml+1 −1 modified@@ -4,7 +4,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <artifactId>spring-security-oauth-tests</artifactId>
tests/xml/approval/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/xml/client/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/xml/common/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/xml/form/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/xml/jdbc/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/xml/jwt/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/xml/mappings/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
tests/xml/pom.xml+2 −2 modified@@ -4,7 +4,7 @@ <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> <packaging>pom</packaging> <modules> @@ -33,7 +33,7 @@ <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> <exclusions> <exclusion> <artifactId>jackson-mapper-asl</artifactId>
tests/xml/vanilla/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.2.3.BUILD-SNAPSHOT</version> + <version>2.2.3.RELEASE</version> </parent> <dependencies>
2b0b8415adcaRelease version 2.3.4.RELEASE
34 files changed · +36 −36
pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <name>OAuth for Spring Security</name> <description>Parent Project for OAuth Support for Spring Security</description> <packaging>pom</packaging> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> <url>http://static.springframework.org/spring-security/oauth</url> <modules>
samples/oauth2/sparklr/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/oauth2/tonr/pom.xml+1 −1 modified@@ -6,7 +6,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/oauth/sparklr/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/oauth/tonr/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> <relativePath>../../..</relativePath> </parent>
samples/pom.xml+1 −1 modified@@ -4,7 +4,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <artifactId>spring-security-oauth-samples</artifactId>
spring-security-oauth2/pom.xml+1 −1 modified@@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <artifactId>spring-security-oauth2</artifactId>
spring-security-oauth/pom.xml+1 −1 modified@@ -4,7 +4,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <artifactId>spring-security-oauth</artifactId>
tests/annotation/approval/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/annotation/client/pom.xml+1 −1 modified@@ -11,7 +11,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <properties>
tests/annotation/common/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/annotation/custom-authentication/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/annotation/custom-grant/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/annotation/form/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/annotation/jaxb/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/annotation/jdbc/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/annotation/jpa/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/annotation/jwt/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/annotation/mappings/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/annotation/multi/pom.xml+1 −1 modified@@ -9,7 +9,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/annotation/pom.xml+2 −2 modified@@ -4,7 +4,7 @@ <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> <packaging>pom</packaging> <modules> @@ -39,7 +39,7 @@ <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> <exclusions> <exclusion> <artifactId>jackson-mapper-asl</artifactId>
tests/annotation/resource/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/annotation/ssl/pom.xml+1 −1 modified@@ -11,7 +11,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/annotation/vanilla/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/pom.xml+1 −1 modified@@ -4,7 +4,7 @@ <parent> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <artifactId>spring-security-oauth-tests</artifactId>
tests/xml/approval/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/xml/client/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/xml/common/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/xml/form/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/xml/jdbc/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/xml/jwt/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/xml/mappings/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
tests/xml/pom.xml+2 −2 modified@@ -4,7 +4,7 @@ <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> <packaging>pom</packaging> <modules> @@ -33,7 +33,7 @@ <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> <exclusions> <exclusion> <artifactId>jackson-mapper-asl</artifactId>
tests/xml/vanilla/pom.xml+1 −1 modified@@ -10,7 +10,7 @@ <parent> <groupId>org.demo</groupId> <artifactId>spring-oauth2-tests-xml-parent</artifactId> - <version>2.3.4.BUILD-SNAPSHOT</version> + <version>2.3.4.RELEASE</version> </parent> <dependencies>
f92223afc716Validate authorization request on approval
2 files changed · +237 −47
spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpoint.java+103 −18 modified@@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -10,23 +10,14 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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.net.URI; -import java.security.Principal; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - 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; @@ -51,6 +42,7 @@ 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; @@ -68,6 +60,16 @@ import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; +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; + /** * <p> * Implementation of the Authorization Endpoint from the OAuth2 specification. Accepts authorization requests, and @@ -86,8 +88,11 @@ * */ @FrameworkEndpoint -@SessionAttributes("authorizationRequest") +@SessionAttributes({AuthorizationEndpoint.AUTHORIZATION_REQUEST_ATTR_NAME, AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME}) 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(); @@ -174,10 +179,10 @@ public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<Strin } } - // 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", authorizationRequest); + // 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, authorizationRequest, (Authentication) principal); @@ -189,6 +194,33 @@ public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<Strin } + Map<String, Object> unmodifiableMap(AuthorizationRequest authorizationRequest) { + Map<String, Object> authorizationRequestMap = new HashMap<String, Object>(); + + 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<String>(authorizationRequest.getResponseTypes()))); + } + if (authorizationRequest.getScope() != null) { + authorizationRequestMap.put(OAuth2Utils.SCOPE, + Collections.unmodifiableSet(new HashSet<String>(authorizationRequest.getScope()))); + } + authorizationRequestMap.put("approved", authorizationRequest.isApproved()); + if (authorizationRequest.getResourceIds() != null) { + authorizationRequestMap.put("resourceIds", + Collections.unmodifiableSet(new HashSet<String>(authorizationRequest.getResourceIds()))); + } + if (authorizationRequest.getAuthorities() != null) { + authorizationRequestMap.put("authorities", + Collections.unmodifiableSet(new HashSet<GrantedAuthority>(authorizationRequest.getAuthorities()))); + } + + return Collections.unmodifiableMap(authorizationRequestMap); + } + @RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL) public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model, SessionStatus sessionStatus, Principal principal) { @@ -199,13 +231,20 @@ public View approveOrDeny(@RequestParam Map<String, String> 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<String, Object> originalAuthorizationRequest = (Map<String, Object>) model.get(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME); + if (isAuthorizationRequestModified(authorizationRequest, originalAuthorizationRequest)) { + throw new InvalidRequestException("Changes were detected from the original authorization request."); + } + try { Set<String> responseTypes = authorizationRequest.getResponseTypes(); @@ -238,6 +277,52 @@ public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, } + private boolean isAuthorizationRequestModified( + AuthorizationRequest authorizationRequest, Map<String, Object> 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<String, Object> model, AuthorizationRequest authorizationRequest, Authentication principal) { @@ -529,7 +614,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; }
spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpointTests.java+134 −29 modified@@ -1,5 +1,5 @@ /* - * Copyright 2006-2011 the original author or authors. + * 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 @@ -16,6 +16,8 @@ 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; @@ -29,6 +31,7 @@ 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; @@ -152,8 +155,9 @@ public void testStartAuthorizationCodeFlowForClientCredentialsFails() throws Exc @Test public void testAuthorizationCodeWithFragment() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put("authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com#bar", null, null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?code=thecode#bar", ((RedirectView) result).getUrl()); @@ -162,9 +166,9 @@ public void testAuthorizationCodeWithFragment() throws Exception { @Test public void testAuthorizationCodeWithQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com?foo=bar", null, null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?foo=bar&code=thecode", ((RedirectView) result).getUrl()); @@ -173,8 +177,9 @@ public void testAuthorizationCodeWithQueryParams() throws Exception { @Test public void testAuthorizationCodeWithTrickyState() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put("authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com", " =?s", null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?code=thecode&state=%20%3D?s", ((RedirectView) result).getUrl()); @@ -183,10 +188,10 @@ public void testAuthorizationCodeWithTrickyState() throws Exception { @Test public void testAuthorizationCodeWithMultipleQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com?foo=bar&bar=foo", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?foo=bar&bar=foo&code=thecode", ((RedirectView) result).getUrl()); @@ -195,10 +200,10 @@ public void testAuthorizationCodeWithMultipleQueryParams() throws Exception { @Test public void testAuthorizationCodeWithTrickyQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com?foo=b =&bar=f $", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://anywhere.com?foo=b =&bar=f $", 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(); @@ -211,10 +216,10 @@ public void testAuthorizationCodeWithTrickyQueryParams() throws Exception { @Test public void testAuthorizationCodeWithTrickyEncodedQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com/path?foo=b%20%3D&bar=f%20$", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest( + "foo", "http://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("http://anywhere.com/path?foo=b%20%3D&bar=f%20$&code=thecode", ((RedirectView) result).getUrl()); @@ -223,10 +228,10 @@ public void testAuthorizationCodeWithTrickyEncodedQueryParams() throws Exception @Test public void testAuthorizationCodeWithMoreTrickyEncodedQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere?t=a%3Db%26ep%3Dtest%2540test.me", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest( + "foo", "http://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("http://anywhere?t=a%3Db%26ep%3Dtest%2540test.me&code=thecode", ((RedirectView) result).getUrl()); @@ -508,15 +513,17 @@ public void testApproveOrDeny() throws Exception { request.setApproved(true); Map<String, String> approvalParameters = new HashMap<String, String>(); approvalParameters.put("user_oauth_approval", "true"); - model.put("authorizationRequest", request); + 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("http://anywhere.com")); } @Test public void testApprovalDenied() throws Exception { - model.put("authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com", null, null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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<String, String> approvalParameters = new HashMap<String, String>(); approvalParameters.put("user_oauth_approval", "false"); View result = endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); @@ -541,7 +548,7 @@ public void testRedirectUriOptionalForAuthorization() throws Exception { .getRequestParameters(), sessionStatus, principal); // RedirectUri parameter should be null (SECOAUTH-333), however the resolvedRedirectUri not AuthorizationRequest authorizationRequest = (AuthorizationRequest) result.getModelMap().get( - "authorizationRequest"); + AUTHORIZATION_REQUEST_ATTR_NAME); assertNull(authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI)); assertEquals("http://anywhere.com", authorizationRequest.getRedirectUri()); } @@ -556,9 +563,107 @@ public void testApproveOrDenyWithOAuth2RequestWithoutRedirectUri() throws Except request.setApproved(true); Map<String, String> approvalParameters = new HashMap<String, String>(); approvalParameters.put("user_oauth_approval", "true"); - model.put("authorizationRequest", request); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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("http://somewhere.com"); // Modify authorization request + Map<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + approvalParameters.put("user_oauth_approval", "true"); + endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); } private class StubAuthorizationCodeServices implements AuthorizationCodeServices {
4082ec7ae3d3Validate authorization request on approval
2 files changed · +237 −47
spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpoint.java+103 −18 modified@@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -10,23 +10,14 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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.net.URI; -import java.security.Principal; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - 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; @@ -51,6 +42,7 @@ 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; @@ -68,6 +60,16 @@ import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; +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; + /** * <p> * Implementation of the Authorization Endpoint from the OAuth2 specification. Accepts authorization requests, and @@ -86,8 +88,11 @@ * */ @FrameworkEndpoint -@SessionAttributes("authorizationRequest") +@SessionAttributes({AuthorizationEndpoint.AUTHORIZATION_REQUEST_ATTR_NAME, AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME}) 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(); @@ -174,10 +179,10 @@ public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<Strin } } - // 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", authorizationRequest); + // 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, authorizationRequest, (Authentication) principal); @@ -189,6 +194,33 @@ public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<Strin } + Map<String, Object> unmodifiableMap(AuthorizationRequest authorizationRequest) { + Map<String, Object> authorizationRequestMap = new HashMap<String, Object>(); + + 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<String>(authorizationRequest.getResponseTypes()))); + } + if (authorizationRequest.getScope() != null) { + authorizationRequestMap.put(OAuth2Utils.SCOPE, + Collections.unmodifiableSet(new HashSet<String>(authorizationRequest.getScope()))); + } + authorizationRequestMap.put("approved", authorizationRequest.isApproved()); + if (authorizationRequest.getResourceIds() != null) { + authorizationRequestMap.put("resourceIds", + Collections.unmodifiableSet(new HashSet<String>(authorizationRequest.getResourceIds()))); + } + if (authorizationRequest.getAuthorities() != null) { + authorizationRequestMap.put("authorities", + Collections.unmodifiableSet(new HashSet<GrantedAuthority>(authorizationRequest.getAuthorities()))); + } + + return Collections.unmodifiableMap(authorizationRequestMap); + } + @RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL) public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model, SessionStatus sessionStatus, Principal principal) { @@ -199,13 +231,20 @@ public View approveOrDeny(@RequestParam Map<String, String> 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<String, Object> originalAuthorizationRequest = (Map<String, Object>) model.get(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME); + if (isAuthorizationRequestModified(authorizationRequest, originalAuthorizationRequest)) { + throw new InvalidRequestException("Changes were detected from the original authorization request."); + } + try { Set<String> responseTypes = authorizationRequest.getResponseTypes(); @@ -238,6 +277,52 @@ public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, } + private boolean isAuthorizationRequestModified( + AuthorizationRequest authorizationRequest, Map<String, Object> 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<String, Object> model, AuthorizationRequest authorizationRequest, Authentication principal) { @@ -531,7 +616,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; }
spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpointTests.java+134 −29 modified@@ -1,5 +1,5 @@ /* - * Copyright 2006-2011 the original author or authors. + * 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 @@ -16,6 +16,8 @@ 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; @@ -29,6 +31,7 @@ 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; @@ -152,8 +155,9 @@ public void testStartAuthorizationCodeFlowForClientCredentialsFails() throws Exc @Test public void testAuthorizationCodeWithFragment() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put("authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com#bar", null, null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?code=thecode#bar", ((RedirectView) result).getUrl()); @@ -162,9 +166,9 @@ public void testAuthorizationCodeWithFragment() throws Exception { @Test public void testAuthorizationCodeWithQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com?foo=bar", null, null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?foo=bar&code=thecode", ((RedirectView) result).getUrl()); @@ -173,8 +177,9 @@ public void testAuthorizationCodeWithQueryParams() throws Exception { @Test public void testAuthorizationCodeWithTrickyState() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put("authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com", " =?s", null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?code=thecode&state=%20%3D?s", ((RedirectView) result).getUrl()); @@ -183,10 +188,10 @@ public void testAuthorizationCodeWithTrickyState() throws Exception { @Test public void testAuthorizationCodeWithMultipleQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com?foo=bar&bar=foo", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?foo=bar&bar=foo&code=thecode", ((RedirectView) result).getUrl()); @@ -195,10 +200,10 @@ public void testAuthorizationCodeWithMultipleQueryParams() throws Exception { @Test public void testAuthorizationCodeWithTrickyQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com?foo=b =&bar=f $", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://anywhere.com?foo=b =&bar=f $", 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(); @@ -211,10 +216,10 @@ public void testAuthorizationCodeWithTrickyQueryParams() throws Exception { @Test public void testAuthorizationCodeWithTrickyEncodedQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com/path?foo=b%20%3D&bar=f%20$", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest( + "foo", "http://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("http://anywhere.com/path?foo=b%20%3D&bar=f%20$&code=thecode", ((RedirectView) result).getUrl()); @@ -223,10 +228,10 @@ public void testAuthorizationCodeWithTrickyEncodedQueryParams() throws Exception @Test public void testAuthorizationCodeWithMoreTrickyEncodedQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere?t=a%3Db%26ep%3Dtest%2540test.me", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest( + "foo", "http://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("http://anywhere?t=a%3Db%26ep%3Dtest%2540test.me&code=thecode", ((RedirectView) result).getUrl()); @@ -508,15 +513,17 @@ public void testApproveOrDeny() throws Exception { request.setApproved(true); Map<String, String> approvalParameters = new HashMap<String, String>(); approvalParameters.put("user_oauth_approval", "true"); - model.put("authorizationRequest", request); + 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("http://anywhere.com")); } @Test public void testApprovalDenied() throws Exception { - model.put("authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com", null, null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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<String, String> approvalParameters = new HashMap<String, String>(); approvalParameters.put("user_oauth_approval", "false"); View result = endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); @@ -541,7 +548,7 @@ public void testRedirectUriOptionalForAuthorization() throws Exception { .getRequestParameters(), sessionStatus, principal); // RedirectUri parameter should be null (SECOAUTH-333), however the resolvedRedirectUri not AuthorizationRequest authorizationRequest = (AuthorizationRequest) result.getModelMap().get( - "authorizationRequest"); + AUTHORIZATION_REQUEST_ATTR_NAME); assertNull(authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI)); assertEquals("http://anywhere.com", authorizationRequest.getRedirectUri()); } @@ -556,9 +563,107 @@ public void testApproveOrDenyWithOAuth2RequestWithoutRedirectUri() throws Except request.setApproved(true); Map<String, String> approvalParameters = new HashMap<String, String>(); approvalParameters.put("user_oauth_approval", "true"); - model.put("authorizationRequest", request); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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("http://somewhere.com"); // Modify authorization request + Map<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + approvalParameters.put("user_oauth_approval", "true"); + endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); } private class StubAuthorizationCodeServices implements AuthorizationCodeServices {
ddd65cd9417aValidate authorization request on approval
2 files changed · +237 −47
spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpoint.java+103 −18 modified@@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -10,23 +10,14 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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.net.URI; -import java.security.Principal; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - 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; @@ -51,6 +42,7 @@ 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; @@ -68,6 +60,16 @@ import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; +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; + /** * <p> * Implementation of the Authorization Endpoint from the OAuth2 specification. Accepts authorization requests, and @@ -86,8 +88,11 @@ * */ @FrameworkEndpoint -@SessionAttributes("authorizationRequest") +@SessionAttributes({AuthorizationEndpoint.AUTHORIZATION_REQUEST_ATTR_NAME, AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME}) 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(); @@ -174,10 +179,10 @@ public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<Strin } } - // 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", authorizationRequest); + // 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, authorizationRequest, (Authentication) principal); @@ -189,6 +194,33 @@ public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<Strin } + Map<String, Object> unmodifiableMap(AuthorizationRequest authorizationRequest) { + Map<String, Object> authorizationRequestMap = new HashMap<String, Object>(); + + 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<String>(authorizationRequest.getResponseTypes()))); + } + if (authorizationRequest.getScope() != null) { + authorizationRequestMap.put(OAuth2Utils.SCOPE, + Collections.unmodifiableSet(new HashSet<String>(authorizationRequest.getScope()))); + } + authorizationRequestMap.put("approved", authorizationRequest.isApproved()); + if (authorizationRequest.getResourceIds() != null) { + authorizationRequestMap.put("resourceIds", + Collections.unmodifiableSet(new HashSet<String>(authorizationRequest.getResourceIds()))); + } + if (authorizationRequest.getAuthorities() != null) { + authorizationRequestMap.put("authorities", + Collections.unmodifiableSet(new HashSet<GrantedAuthority>(authorizationRequest.getAuthorities()))); + } + + return Collections.unmodifiableMap(authorizationRequestMap); + } + @RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL) public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model, SessionStatus sessionStatus, Principal principal) { @@ -199,13 +231,20 @@ public View approveOrDeny(@RequestParam Map<String, String> 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<String, Object> originalAuthorizationRequest = (Map<String, Object>) model.get(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME); + if (isAuthorizationRequestModified(authorizationRequest, originalAuthorizationRequest)) { + throw new InvalidRequestException("Changes were detected from the original authorization request."); + } + try { Set<String> responseTypes = authorizationRequest.getResponseTypes(); @@ -238,6 +277,52 @@ public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, } + private boolean isAuthorizationRequestModified( + AuthorizationRequest authorizationRequest, Map<String, Object> 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<String, Object> model, AuthorizationRequest authorizationRequest, Authentication principal) { @@ -529,7 +614,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; }
spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpointTests.java+134 −29 modified@@ -1,5 +1,5 @@ /* - * Copyright 2006-2011 the original author or authors. + * 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 @@ -16,6 +16,8 @@ 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; @@ -29,6 +31,7 @@ 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; @@ -152,8 +155,9 @@ public void testStartAuthorizationCodeFlowForClientCredentialsFails() throws Exc @Test public void testAuthorizationCodeWithFragment() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put("authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com#bar", null, null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?code=thecode#bar", ((RedirectView) result).getUrl()); @@ -162,9 +166,9 @@ public void testAuthorizationCodeWithFragment() throws Exception { @Test public void testAuthorizationCodeWithQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com?foo=bar", null, null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?foo=bar&code=thecode", ((RedirectView) result).getUrl()); @@ -173,8 +177,9 @@ public void testAuthorizationCodeWithQueryParams() throws Exception { @Test public void testAuthorizationCodeWithTrickyState() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put("authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com", " =?s", null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?code=thecode&state=%20%3D?s", ((RedirectView) result).getUrl()); @@ -183,10 +188,10 @@ public void testAuthorizationCodeWithTrickyState() throws Exception { @Test public void testAuthorizationCodeWithMultipleQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com?foo=bar&bar=foo", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?foo=bar&bar=foo&code=thecode", ((RedirectView) result).getUrl()); @@ -195,10 +200,10 @@ public void testAuthorizationCodeWithMultipleQueryParams() throws Exception { @Test public void testAuthorizationCodeWithTrickyQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com?foo=b =&bar=f $", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://anywhere.com?foo=b =&bar=f $", 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(); @@ -211,10 +216,10 @@ public void testAuthorizationCodeWithTrickyQueryParams() throws Exception { @Test public void testAuthorizationCodeWithTrickyEncodedQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com/path?foo=b%20%3D&bar=f%20$", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest( + "foo", "http://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("http://anywhere.com/path?foo=b%20%3D&bar=f%20$&code=thecode", ((RedirectView) result).getUrl()); @@ -223,10 +228,10 @@ public void testAuthorizationCodeWithTrickyEncodedQueryParams() throws Exception @Test public void testAuthorizationCodeWithMoreTrickyEncodedQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere?t=a%3Db%26ep%3Dtest%2540test.me", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest( + "foo", "http://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("http://anywhere?t=a%3Db%26ep%3Dtest%2540test.me&code=thecode", ((RedirectView) result).getUrl()); @@ -508,15 +513,17 @@ public void testApproveOrDeny() throws Exception { request.setApproved(true); Map<String, String> approvalParameters = new HashMap<String, String>(); approvalParameters.put("user_oauth_approval", "true"); - model.put("authorizationRequest", request); + 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("http://anywhere.com")); } @Test public void testApprovalDenied() throws Exception { - model.put("authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com", null, null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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<String, String> approvalParameters = new HashMap<String, String>(); approvalParameters.put("user_oauth_approval", "false"); View result = endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); @@ -541,7 +548,7 @@ public void testRedirectUriOptionalForAuthorization() throws Exception { .getRequestParameters(), sessionStatus, principal); // RedirectUri parameter should be null (SECOAUTH-333), however the resolvedRedirectUri not AuthorizationRequest authorizationRequest = (AuthorizationRequest) result.getModelMap().get( - "authorizationRequest"); + AUTHORIZATION_REQUEST_ATTR_NAME); assertNull(authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI)); assertEquals("http://anywhere.com", authorizationRequest.getRedirectUri()); } @@ -556,9 +563,107 @@ public void testApproveOrDenyWithOAuth2RequestWithoutRedirectUri() throws Except request.setApproved(true); Map<String, String> approvalParameters = new HashMap<String, String>(); approvalParameters.put("user_oauth_approval", "true"); - model.put("authorizationRequest", request); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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("http://somewhere.com"); // Modify authorization request + Map<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + approvalParameters.put("user_oauth_approval", "true"); + endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); } private class StubAuthorizationCodeServices implements AuthorizationCodeServices {
623776689fdcValidate authorization request on approval
2 files changed · +237 −47
spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpoint.java+103 −18 modified@@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -10,23 +10,14 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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.net.URI; -import java.security.Principal; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - 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; @@ -51,6 +42,7 @@ 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; @@ -68,6 +60,16 @@ import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; +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; + /** * <p> * Implementation of the Authorization Endpoint from the OAuth2 specification. Accepts authorization requests, and @@ -86,8 +88,11 @@ * */ @FrameworkEndpoint -@SessionAttributes("authorizationRequest") +@SessionAttributes({AuthorizationEndpoint.AUTHORIZATION_REQUEST_ATTR_NAME, AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME}) 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(); @@ -174,10 +179,10 @@ public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<Strin } } - // 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", authorizationRequest); + // 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, authorizationRequest, (Authentication) principal); @@ -189,6 +194,33 @@ public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<Strin } + Map<String, Object> unmodifiableMap(AuthorizationRequest authorizationRequest) { + Map<String, Object> authorizationRequestMap = new HashMap<String, Object>(); + + 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<String>(authorizationRequest.getResponseTypes()))); + } + if (authorizationRequest.getScope() != null) { + authorizationRequestMap.put(OAuth2Utils.SCOPE, + Collections.unmodifiableSet(new HashSet<String>(authorizationRequest.getScope()))); + } + authorizationRequestMap.put("approved", authorizationRequest.isApproved()); + if (authorizationRequest.getResourceIds() != null) { + authorizationRequestMap.put("resourceIds", + Collections.unmodifiableSet(new HashSet<String>(authorizationRequest.getResourceIds()))); + } + if (authorizationRequest.getAuthorities() != null) { + authorizationRequestMap.put("authorities", + Collections.unmodifiableSet(new HashSet<GrantedAuthority>(authorizationRequest.getAuthorities()))); + } + + return Collections.unmodifiableMap(authorizationRequestMap); + } + @RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL) public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model, SessionStatus sessionStatus, Principal principal) { @@ -199,13 +231,20 @@ public View approveOrDeny(@RequestParam Map<String, String> 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<String, Object> originalAuthorizationRequest = (Map<String, Object>) model.get(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME); + if (isAuthorizationRequestModified(authorizationRequest, originalAuthorizationRequest)) { + throw new InvalidRequestException("Changes were detected from the original authorization request."); + } + try { Set<String> responseTypes = authorizationRequest.getResponseTypes(); @@ -238,6 +277,52 @@ public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, } + private boolean isAuthorizationRequestModified( + AuthorizationRequest authorizationRequest, Map<String, Object> 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<String, Object> model, AuthorizationRequest authorizationRequest, Authentication principal) { @@ -529,7 +614,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; }
spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpointTests.java+134 −29 modified@@ -1,5 +1,5 @@ /* - * Copyright 2006-2011 the original author or authors. + * 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 @@ -16,6 +16,8 @@ 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; @@ -29,6 +31,7 @@ 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; @@ -152,8 +155,9 @@ public void testStartAuthorizationCodeFlowForClientCredentialsFails() throws Exc @Test public void testAuthorizationCodeWithFragment() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put("authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com#bar", null, null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?code=thecode#bar", ((RedirectView) result).getUrl()); @@ -162,9 +166,9 @@ public void testAuthorizationCodeWithFragment() throws Exception { @Test public void testAuthorizationCodeWithQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com?foo=bar", null, null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?foo=bar&code=thecode", ((RedirectView) result).getUrl()); @@ -173,8 +177,9 @@ public void testAuthorizationCodeWithQueryParams() throws Exception { @Test public void testAuthorizationCodeWithTrickyState() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put("authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com", " =?s", null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?code=thecode&state=%20%3D?s", ((RedirectView) result).getUrl()); @@ -183,10 +188,10 @@ public void testAuthorizationCodeWithTrickyState() throws Exception { @Test public void testAuthorizationCodeWithMultipleQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com?foo=bar&bar=foo", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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("http://anywhere.com?foo=bar&bar=foo&code=thecode", ((RedirectView) result).getUrl()); @@ -195,10 +200,10 @@ public void testAuthorizationCodeWithMultipleQueryParams() throws Exception { @Test public void testAuthorizationCodeWithTrickyQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com?foo=b =&bar=f $", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://anywhere.com?foo=b =&bar=f $", 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(); @@ -211,10 +216,10 @@ public void testAuthorizationCodeWithTrickyQueryParams() throws Exception { @Test public void testAuthorizationCodeWithTrickyEncodedQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com/path?foo=b%20%3D&bar=f%20$", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest( + "foo", "http://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("http://anywhere.com/path?foo=b%20%3D&bar=f%20$&code=thecode", ((RedirectView) result).getUrl()); @@ -223,10 +228,10 @@ public void testAuthorizationCodeWithTrickyEncodedQueryParams() throws Exception @Test public void testAuthorizationCodeWithMoreTrickyEncodedQueryParams() throws Exception { endpoint.setAuthorizationCodeServices(new StubAuthorizationCodeServices()); - model.put( - "authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere?t=a%3Db%26ep%3Dtest%2540test.me", null, null, - Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest( + "foo", "http://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("http://anywhere?t=a%3Db%26ep%3Dtest%2540test.me&code=thecode", ((RedirectView) result).getUrl()); @@ -508,15 +513,17 @@ public void testApproveOrDeny() throws Exception { request.setApproved(true); Map<String, String> approvalParameters = new HashMap<String, String>(); approvalParameters.put("user_oauth_approval", "true"); - model.put("authorizationRequest", request); + 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("http://anywhere.com")); } @Test public void testApprovalDenied() throws Exception { - model.put("authorizationRequest", - getAuthorizationRequest("foo", "http://anywhere.com", null, null, Collections.singleton("code"))); + AuthorizationRequest request = getAuthorizationRequest("foo", "http://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<String, String> approvalParameters = new HashMap<String, String>(); approvalParameters.put("user_oauth_approval", "false"); View result = endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); @@ -541,7 +548,7 @@ public void testRedirectUriOptionalForAuthorization() throws Exception { .getRequestParameters(), sessionStatus, principal); // RedirectUri parameter should be null (SECOAUTH-333), however the resolvedRedirectUri not AuthorizationRequest authorizationRequest = (AuthorizationRequest) result.getModelMap().get( - "authorizationRequest"); + AUTHORIZATION_REQUEST_ATTR_NAME); assertNull(authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI)); assertEquals("http://anywhere.com", authorizationRequest.getRedirectUri()); } @@ -556,9 +563,107 @@ public void testApproveOrDenyWithOAuth2RequestWithoutRedirectUri() throws Except request.setApproved(true); Map<String, String> approvalParameters = new HashMap<String, String>(); approvalParameters.put("user_oauth_approval", "true"); - model.put("authorizationRequest", request); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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("http://somewhere.com"); // Modify authorization request + Map<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + 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", "http://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<String, String> approvalParameters = new HashMap<String, String>(); + approvalParameters.put("user_oauth_approval", "true"); + endpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); } private class StubAuthorizationCodeServices implements AuthorizationCodeServices {
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- access.redhat.com/errata/RHSA-2019:2413ghsavendor-advisoryx_refsource_REDHATWEB
- github.com/advisories/GHSA-h8w4-qv99-f7vjghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-15758ghsaADVISORY
- www.securityfocus.com/bid/105687ghsavdb-entryx_refsource_BIDWEB
- github.com/spring-attic/spring-security-oauth/commit/4082ec7ae3d39198a47b5c803ccb20dacefb0b0ghsaWEB
- github.com/spring-attic/spring-security-oauth/commit/623776689fdcc8047f5a908c71f348e1f172a97ghsaWEB
- github.com/spring-attic/spring-security-oauth/commit/ddd65cd9417ae1e4a69e4193a622300db38e2efghsaWEB
- github.com/spring-attic/spring-security-oauth/commit/f92223afc71687bd3156298054903f50aa71fbfghsaWEB
- pivotal.io/security/cve-2018-15758ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.