View Javadoc
1   /*
2    * Copyright 2002-2011 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.springframework.security.oauth2.client.token;
17  
18  import java.util.Collections;
19  import java.util.List;
20  
21  import org.springframework.security.access.AccessDeniedException;
22  import org.springframework.security.authentication.AnonymousAuthenticationToken;
23  import org.springframework.security.authentication.InsufficientAuthenticationException;
24  import org.springframework.security.core.Authentication;
25  import org.springframework.security.core.context.SecurityContextHolder;
26  import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException;
27  import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
28  import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
29  import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
30  import org.springframework.security.oauth2.common.OAuth2AccessToken;
31  import org.springframework.security.oauth2.common.OAuth2RefreshToken;
32  import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
33  
34  /**
35   * A chain of OAuth2 access token providers. This implementation will iterate through its
36   * chain to find the first provider that supports the resource and use it to obtain the
37   * access token. Note that the order of the chain is relevant.
38   *
39   * @author Ryan Heaton
40   * @author Dave Syer
41   */
42  public class AccessTokenProviderChain extends OAuth2AccessTokenSupport
43  		implements AccessTokenProvider {
44  
45  	private final List<AccessTokenProvider> chain;
46  
47  	private ClientTokenServices clientTokenServices;
48  
49  	public AccessTokenProviderChain(List<? extends AccessTokenProvider> chain) {
50  		this.chain = chain == null ? Collections.<AccessTokenProvider> emptyList()
51  				: Collections.unmodifiableList(chain);
52  	}
53  
54  	/**
55  	 * Token services for long-term persistence of access tokens.
56  	 *
57  	 * @param clientTokenServices the clientTokenServices to set
58  	 */
59  	public void setClientTokenServices(ClientTokenServices clientTokenServices) {
60  		this.clientTokenServices = clientTokenServices;
61  	}
62  
63  	public boolean supportsResource(OAuth2ProtectedResourceDetails resource) {
64  		for (AccessTokenProvider tokenProvider : chain) {
65  			if (tokenProvider.supportsResource(resource)) {
66  				return true;
67  			}
68  		}
69  		return false;
70  	}
71  
72  	public boolean supportsRefresh(OAuth2ProtectedResourceDetails resource) {
73  		for (AccessTokenProvider tokenProvider : chain) {
74  			if (tokenProvider.supportsRefresh(resource)) {
75  				return true;
76  			}
77  		}
78  		return false;
79  	}
80  
81  	public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails resource,
82  			AccessTokenRequest request)
83  			throws UserRedirectRequiredException, AccessDeniedException {
84  
85  		OAuth2AccessToken accessToken = null;
86  		OAuth2AccessToken existingToken = null;
87  		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
88  
89  		if (auth instanceof AnonymousAuthenticationToken) {
90  			if (!resource.isClientOnly()) {
91  				throw new InsufficientAuthenticationException(
92  						"Authentication is required to obtain an access token (anonymous not allowed)");
93  			}
94  		}
95  
96  		if (resource.isClientOnly() || (auth != null && auth.isAuthenticated())) {
97  			existingToken = request.getExistingToken();
98  			if (existingToken == null && clientTokenServices != null) {
99  				existingToken = clientTokenServices.getAccessToken(resource, auth);
100 			}
101 
102 			if (existingToken != null) {
103 				if (existingToken.isExpired()) {
104 					if (clientTokenServices != null) {
105 						clientTokenServices.removeAccessToken(resource, auth);
106 					}
107 					OAuth2RefreshToken refreshToken = existingToken.getRefreshToken();
108 					if (refreshToken != null && !resource.isClientOnly()) {
109 						accessToken = refreshAccessToken(resource, refreshToken, request);
110 					}
111 				}
112 				else {
113 					accessToken = existingToken;
114 				}
115 			}
116 		}
117 		// Give unauthenticated users a chance to get a token and be redirected
118 
119 		if (accessToken == null) {
120 			// looks like we need to try to obtain a new token.
121 			accessToken = obtainNewAccessTokenInternal(resource, request);
122 
123 			if (accessToken == null) {
124 				throw new IllegalStateException(
125 						"An OAuth 2 access token must be obtained or an exception thrown.");
126 			}
127 		}
128 
129 		if (clientTokenServices != null
130 				&& (resource.isClientOnly() || auth != null && auth.isAuthenticated())) {
131 			clientTokenServices.saveAccessToken(resource, auth, accessToken);
132 		}
133 
134 		return accessToken;
135 	}
136 
137 	protected OAuth2AccessToken obtainNewAccessTokenInternal(
138 			OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
139 			throws UserRedirectRequiredException, AccessDeniedException {
140 
141 		if (request.isError()) {
142 			// there was an oauth error...
143 			throw OAuth2Exception.valueOf(request.toSingleValueMap());
144 		}
145 
146 		for (AccessTokenProvider tokenProvider : chain) {
147 			if (tokenProvider.supportsResource(details)) {
148 				return tokenProvider.obtainAccessToken(details, request);
149 			}
150 		}
151 
152 		throw new OAuth2AccessDeniedException(
153 				"Unable to obtain a new access token for resource '" + details.getId()
154 						+ "'. The provider manager is not configured to support it.",
155 				details);
156 	}
157 
158 	/**
159 	 * Obtain a new access token for the specified resource using the refresh token.
160 	 *
161 	 * @param resource The resource.
162 	 * @param refreshToken The refresh token.
163 	 * @return The access token, or null if failed.
164 	 * @throws UserRedirectRequiredException
165 	 */
166 	public OAuth2AccessToken refreshAccessToken(OAuth2ProtectedResourceDetails resource,
167 			OAuth2RefreshToken refreshToken, AccessTokenRequest request)
168 			throws UserRedirectRequiredException {
169 		for (AccessTokenProvider tokenProvider : chain) {
170 			if (tokenProvider.supportsRefresh(resource)) {
171 				DefaultOAuth2AccessToken refreshedAccessToken = new DefaultOAuth2AccessToken(
172 						tokenProvider.refreshAccessToken(resource, refreshToken,
173 								request));
174 				if (refreshedAccessToken.getRefreshToken() == null) {
175 					// Fixes gh-712
176 					refreshedAccessToken.setRefreshToken(refreshToken);
177 				}
178 				return refreshedAccessToken;
179 			}
180 		}
181 		throw new OAuth2AccessDeniedException(
182 				"Unable to obtain a new access token for resource '" + resource.getId()
183 						+ "'. The provider manager is not configured to support it.",
184 				resource);
185 	}
186 
187 }