View Javadoc
1   /*
2    * Copyright 2002-2019 the original author or authors.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5    * the License. You may obtain a copy of the License at
6    * 
7    * https://www.apache.org/licenses/LICENSE-2.0
8    * 
9    * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10   * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11   * specific language governing permissions and limitations under the License.
12   */
13  package org.springframework.security.oauth2.provider.endpoint;
14  
15  import org.springframework.http.HttpHeaders;
16  import org.springframework.http.HttpStatus;
17  import org.springframework.http.ResponseEntity;
18  import org.springframework.security.access.AccessDeniedException;
19  import org.springframework.security.authentication.InsufficientAuthenticationException;
20  import org.springframework.security.core.Authentication;
21  import org.springframework.security.core.AuthenticationException;
22  import org.springframework.security.core.GrantedAuthority;
23  import org.springframework.security.oauth2.common.OAuth2AccessToken;
24  import org.springframework.security.oauth2.common.exceptions.BadClientCredentialsException;
25  import org.springframework.security.oauth2.common.exceptions.ClientAuthenticationException;
26  import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
27  import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
28  import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
29  import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException;
30  import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
31  import org.springframework.security.oauth2.common.exceptions.UnsupportedResponseTypeException;
32  import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException;
33  import org.springframework.security.oauth2.common.util.OAuth2Utils;
34  import org.springframework.security.oauth2.provider.AuthorizationRequest;
35  import org.springframework.security.oauth2.provider.ClientDetails;
36  import org.springframework.security.oauth2.provider.ClientRegistrationException;
37  import org.springframework.security.oauth2.provider.OAuth2Authentication;
38  import org.springframework.security.oauth2.provider.OAuth2Request;
39  import org.springframework.security.oauth2.provider.OAuth2RequestValidator;
40  import org.springframework.security.oauth2.provider.TokenRequest;
41  import org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler;
42  import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
43  import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
44  import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
45  import org.springframework.security.oauth2.provider.implicit.ImplicitTokenRequest;
46  import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator;
47  import org.springframework.util.ObjectUtils;
48  import org.springframework.util.StringUtils;
49  import org.springframework.web.HttpSessionRequiredException;
50  import org.springframework.web.bind.annotation.ExceptionHandler;
51  import org.springframework.web.bind.annotation.RequestMapping;
52  import org.springframework.web.bind.annotation.RequestMethod;
53  import org.springframework.web.bind.annotation.RequestParam;
54  import org.springframework.web.bind.annotation.SessionAttributes;
55  import org.springframework.web.bind.support.DefaultSessionAttributeStore;
56  import org.springframework.web.bind.support.SessionAttributeStore;
57  import org.springframework.web.bind.support.SessionStatus;
58  import org.springframework.web.context.request.RequestContextHolder;
59  import org.springframework.web.context.request.ServletRequestAttributes;
60  import org.springframework.web.context.request.ServletWebRequest;
61  import org.springframework.web.servlet.ModelAndView;
62  import org.springframework.web.servlet.View;
63  import org.springframework.web.servlet.view.RedirectView;
64  import org.springframework.web.util.UriComponents;
65  import org.springframework.web.util.UriComponentsBuilder;
66  
67  import javax.servlet.http.HttpServletResponse;
68  import java.net.URI;
69  import java.security.Principal;
70  import java.util.Collections;
71  import java.util.Date;
72  import java.util.HashMap;
73  import java.util.HashSet;
74  import java.util.LinkedHashMap;
75  import java.util.Map;
76  import java.util.Set;
77  
78  /**
79   * <p>
80   * Implementation of the Authorization Endpoint from the OAuth2 specification. Accepts authorization requests, and
81   * handles user approval if the grant type is authorization code. The tokens themselves are obtained from the
82   * {@link TokenEndpoint Token Endpoint}, except in the implicit grant type (where they come from the Authorization
83   * Endpoint via <code>response_type=token</code>.
84   * </p>
85   * 
86   * <p>
87   * This endpoint should be secured so that it is only accessible to fully authenticated users (as a minimum requirement)
88   * since it represents a request from a valid user to act on his or her behalf.
89   * </p>
90   * 
91   * @author Dave Syer
92   * @author Vladimir Kryachko
93   * 
94   */
95  @FrameworkEndpoint
96  @SessionAttributes({AuthorizationEndpoint.AUTHORIZATION_REQUEST_ATTR_NAME, AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME})
97  public class AuthorizationEndpoint extends AbstractEndpoint {
98  	static final String AUTHORIZATION_REQUEST_ATTR_NAME = "authorizationRequest";
99  
100 	static final String ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME = "org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST";
101 
102 	private AuthorizationCodeServices authorizationCodeServices = new InMemoryAuthorizationCodeServices();
103 
104 	private RedirectResolver redirectResolver = new DefaultRedirectResolver();
105 
106 	private UserApprovalHandler userApprovalHandler = new DefaultUserApprovalHandler();
107 
108 	private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
109 
110 	private OAuth2RequestValidator oauth2RequestValidator = new DefaultOAuth2RequestValidator();
111 
112 	private String userApprovalPage = "forward:/oauth/confirm_access";
113 
114 	private String errorPage = "forward:/oauth/error";
115 
116 	private Object implicitLock = new Object();
117 
118 	public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
119 		this.sessionAttributeStore = sessionAttributeStore;
120 	}
121 
122 	public void setErrorPage(String errorPage) {
123 		this.errorPage = errorPage;
124 	}
125 
126 	@RequestMapping(value = "/oauth/authorize")
127 	public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
128 			SessionStatus sessionStatus, Principal principal) {
129 
130 		// Pull out the authorization request first, using the OAuth2RequestFactory. All further logic should
131 		// query off of the authorization request instead of referring back to the parameters map. The contents of the
132 		// parameters map will be stored without change in the AuthorizationRequest object once it is created.
133 		AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);
134 
135 		Set<String> responseTypes = authorizationRequest.getResponseTypes();
136 
137 		if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
138 			throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
139 		}
140 
141 		if (authorizationRequest.getClientId() == null) {
142 			throw new InvalidClientException("A client id must be provided");
143 		}
144 
145 		try {
146 
147 			if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
148 				throw new InsufficientAuthenticationException(
149 						"User must be authenticated with Spring Security before authorization can be completed.");
150 			}
151 
152 			ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());
153 
154 			// The resolved redirect URI is either the redirect_uri from the parameters or the one from
155 			// clientDetails. Either way we need to store it on the AuthorizationRequest.
156 			String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
157 			String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
158 			if (!StringUtils.hasText(resolvedRedirect)) {
159 				throw new RedirectMismatchException(
160 						"A redirectUri must be either supplied or preconfigured in the ClientDetails");
161 			}
162 			authorizationRequest.setRedirectUri(resolvedRedirect);
163 
164 			// We intentionally only validate the parameters requested by the client (ignoring any data that may have
165 			// been added to the request by the manager).
166 			oauth2RequestValidator.validateScope(authorizationRequest, client);
167 
168 			// Some systems may allow for approval decisions to be remembered or approved by default. Check for
169 			// such logic here, and set the approved flag on the authorization request accordingly.
170 			authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,
171 					(Authentication) principal);
172 			// TODO: is this call necessary?
173 			boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
174 			authorizationRequest.setApproved(approved);
175 
176 			// Validation is all done, so we can check for auto approval...
177 			if (authorizationRequest.isApproved()) {
178 				if (responseTypes.contains("token")) {
179 					return getImplicitGrantResponse(authorizationRequest);
180 				}
181 				if (responseTypes.contains("code")) {
182 					return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
183 							(Authentication) principal));
184 				}
185 			}
186 
187 			// Store authorizationRequest AND an immutable Map of authorizationRequest in session
188 			// which will be used to validate against in approveOrDeny()
189 			model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
190 			model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, unmodifiableMap(authorizationRequest));
191 
192 			return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);
193 
194 		}
195 		catch (RuntimeException e) {
196 			sessionStatus.setComplete();
197 			throw e;
198 		}
199 
200 	}
201 
202 	Map<String, Object> unmodifiableMap(AuthorizationRequest authorizationRequest) {
203 		Map<String, Object> authorizationRequestMap = new HashMap<String, Object>();
204 
205 		authorizationRequestMap.put(OAuth2Utils.CLIENT_ID, authorizationRequest.getClientId());
206 		authorizationRequestMap.put(OAuth2Utils.STATE, authorizationRequest.getState());
207 		authorizationRequestMap.put(OAuth2Utils.REDIRECT_URI, authorizationRequest.getRedirectUri());
208 		if (authorizationRequest.getResponseTypes() != null) {
209 			authorizationRequestMap.put(OAuth2Utils.RESPONSE_TYPE,
210 					Collections.unmodifiableSet(new HashSet<String>(authorizationRequest.getResponseTypes())));
211 		}
212 		if (authorizationRequest.getScope() != null) {
213 			authorizationRequestMap.put(OAuth2Utils.SCOPE,
214 					Collections.unmodifiableSet(new HashSet<String>(authorizationRequest.getScope())));
215 		}
216 		authorizationRequestMap.put("approved", authorizationRequest.isApproved());
217 		if (authorizationRequest.getResourceIds() != null) {
218 			authorizationRequestMap.put("resourceIds",
219 					Collections.unmodifiableSet(new HashSet<String>(authorizationRequest.getResourceIds())));
220 		}
221 		if (authorizationRequest.getAuthorities() != null) {
222 			authorizationRequestMap.put("authorities",
223 					Collections.unmodifiableSet(new HashSet<GrantedAuthority>(authorizationRequest.getAuthorities())));
224 		}
225 
226 		return Collections.unmodifiableMap(authorizationRequestMap);
227 	}
228 
229 	@RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)
230 	public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model,
231 			SessionStatus sessionStatus, Principal principal) {
232 
233 		if (!(principal instanceof Authentication)) {
234 			sessionStatus.setComplete();
235 			throw new InsufficientAuthenticationException(
236 					"User must be authenticated with Spring Security before authorizing an access token.");
237 		}
238 
239 		AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get(AUTHORIZATION_REQUEST_ATTR_NAME);
240 
241 		if (authorizationRequest == null) {
242 			sessionStatus.setComplete();
243 			throw new InvalidRequestException("Cannot approve uninitialized authorization request.");
244 		}
245 
246 		// Check to ensure the Authorization Request was not modified during the user approval step
247 		@SuppressWarnings("unchecked")
248 		Map<String, Object> originalAuthorizationRequest = (Map<String, Object>) model.get(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME);
249 		if (isAuthorizationRequestModified(authorizationRequest, originalAuthorizationRequest)) {
250 			throw new InvalidRequestException("Changes were detected from the original authorization request.");
251 		}
252 
253 		try {
254 			Set<String> responseTypes = authorizationRequest.getResponseTypes();
255 
256 			authorizationRequest.setApprovalParameters(approvalParameters);
257 			authorizationRequest = userApprovalHandler.updateAfterApproval(authorizationRequest,
258 					(Authentication) principal);
259 			boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
260 			authorizationRequest.setApproved(approved);
261 
262 			if (authorizationRequest.getRedirectUri() == null) {
263 				sessionStatus.setComplete();
264 				throw new InvalidRequestException("Cannot approve request when no redirect URI is provided.");
265 			}
266 
267 			if (!authorizationRequest.isApproved()) {
268 				RedirectView redirectView = new RedirectView(getUnsuccessfulRedirect(authorizationRequest,
269 						new UserDeniedAuthorizationException("User denied access"), responseTypes.contains("token")),
270 						false, true, false);
271 				redirectView.setStatusCode(HttpStatus.SEE_OTHER);
272 				return redirectView;
273 			}
274 
275 			if (responseTypes.contains("token")) {
276 				return getImplicitGrantResponse(authorizationRequest).getView();
277 			}
278 
279 			return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal);
280 		}
281 		finally {
282 			sessionStatus.setComplete();
283 		}
284 
285 	}
286 
287 	private boolean isAuthorizationRequestModified(
288 			AuthorizationRequest authorizationRequest, Map<String, Object> originalAuthorizationRequest) {
289 		if (!ObjectUtils.nullSafeEquals(
290 				authorizationRequest.getClientId(),
291 				originalAuthorizationRequest.get(OAuth2Utils.CLIENT_ID))) {
292 			return true;
293 		}
294 		if (!ObjectUtils.nullSafeEquals(
295 				authorizationRequest.getState(),
296 				originalAuthorizationRequest.get(OAuth2Utils.STATE))) {
297 			return true;
298 		}
299 		if (!ObjectUtils.nullSafeEquals(
300 				authorizationRequest.getRedirectUri(),
301 				originalAuthorizationRequest.get(OAuth2Utils.REDIRECT_URI))) {
302 			return true;
303 		}
304 		if (!ObjectUtils.nullSafeEquals(
305 				authorizationRequest.getResponseTypes(),
306 				originalAuthorizationRequest.get(OAuth2Utils.RESPONSE_TYPE))) {
307 			return true;
308 		}
309 		if (!ObjectUtils.nullSafeEquals(
310 				authorizationRequest.getScope(),
311 				originalAuthorizationRequest.get(OAuth2Utils.SCOPE))) {
312 			return true;
313 		}
314 		if (!ObjectUtils.nullSafeEquals(
315 				authorizationRequest.isApproved(),
316 				originalAuthorizationRequest.get("approved"))) {
317 			return true;
318 		}
319 		if (!ObjectUtils.nullSafeEquals(
320 				authorizationRequest.getResourceIds(),
321 				originalAuthorizationRequest.get("resourceIds"))) {
322 			return true;
323 		}
324 		if (!ObjectUtils.nullSafeEquals(
325 				authorizationRequest.getAuthorities(),
326 				originalAuthorizationRequest.get("authorities"))) {
327 			return true;
328 		}
329 
330 		return false;
331 	}
332 
333 	// We need explicit approval from the user.
334 	private ModelAndView getUserApprovalPageResponse(Map<String, Object> model,
335 			AuthorizationRequest authorizationRequest, Authentication principal) {
336 		if (logger.isDebugEnabled()) {
337 			logger.debug("Loading user approval page: " + userApprovalPage);
338 		}
339 		model.putAll(userApprovalHandler.getUserApprovalRequest(authorizationRequest, principal));
340 		return new ModelAndView(userApprovalPage, model);
341 	}
342 
343 	// We can grant a token and return it with implicit approval.
344 	private ModelAndView getImplicitGrantResponse(AuthorizationRequest authorizationRequest) {
345 		try {
346 			TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(authorizationRequest, "implicit");
347 			OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest);
348 			OAuth2AccessToken accessToken = getAccessTokenForImplicitGrant(tokenRequest, storedOAuth2Request);
349 			if (accessToken == null) {
350 				throw new UnsupportedResponseTypeException("Unsupported response type: token");
351 			}
352 			setCacheControlHeaders();
353 			RedirectView redirectView = new RedirectView(appendAccessToken(authorizationRequest, accessToken), false, true,
354 				false);
355 			redirectView.setStatusCode(HttpStatus.SEE_OTHER);
356 			return new ModelAndView(redirectView);
357 		}
358 		catch (OAuth2Exception e) {
359 				RedirectView redirectView = new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, true), false,
360 					true, false);
361 				redirectView.setStatusCode(HttpStatus.SEE_OTHER);
362 				return new ModelAndView(redirectView);
363 		}
364 	}
365 
366 	private OAuth2AccessToken getAccessTokenForImplicitGrant(TokenRequest tokenRequest,
367 			OAuth2Request storedOAuth2Request) {
368 		OAuth2AccessToken accessToken = null;
369 		// These 1 method calls have to be atomic, otherwise the ImplicitGrantService can have a race condition where
370 		// one thread removes the token request before another has a chance to redeem it.
371 		synchronized (this.implicitLock) {
372 			accessToken = getTokenGranter().grant("implicit",
373 					new ImplicitTokenRequest(tokenRequest, storedOAuth2Request));
374 		}
375 		return accessToken;
376 	}
377 
378 	private View getAuthorizationCodeResponse(AuthorizationRequest authorizationRequest, Authentication authUser) {
379 		try {
380 				RedirectView redirectView = new RedirectView(getSuccessfulRedirect(authorizationRequest,
381 					generateCode(authorizationRequest, authUser)), false, true, false);
382 				redirectView.setStatusCode(HttpStatus.SEE_OTHER);
383 				return redirectView;
384 		}
385 		catch (OAuth2Exception e) {
386 				RedirectView redirectView = new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, false), false, true, false);
387 				redirectView.setStatusCode(HttpStatus.SEE_OTHER);
388 				return redirectView;
389 		}
390 	}
391 
392 	private String appendAccessToken(AuthorizationRequest authorizationRequest, OAuth2AccessToken accessToken) {
393 
394 		Map<String, Object> vars = new LinkedHashMap<String, Object>();
395 		Map<String, String> keys = new HashMap<String, String>();
396 
397 		if (accessToken == null) {
398 			throw new InvalidRequestException("An implicit grant could not be made");
399 		}
400 
401 		vars.put("access_token", accessToken.getValue());
402 		vars.put("token_type", accessToken.getTokenType());
403 		String state = authorizationRequest.getState();
404 
405 		if (state != null) {
406 			vars.put("state", state);
407 		}
408 		Date expiration = accessToken.getExpiration();
409 		if (expiration != null) {
410 			long expires_in = (expiration.getTime() - System.currentTimeMillis()) / 1000;
411 			vars.put("expires_in", expires_in);
412 		}
413 		String originalScope = authorizationRequest.getRequestParameters().get(OAuth2Utils.SCOPE);
414 		if (originalScope == null || !OAuth2Utils.parseParameterList(originalScope).equals(accessToken.getScope())) {
415 			vars.put("scope", OAuth2Utils.formatParameterList(accessToken.getScope()));
416 		}
417 		Map<String, Object> additionalInformation = accessToken.getAdditionalInformation();
418 		for (String key : additionalInformation.keySet()) {
419 			Object value = additionalInformation.get(key);
420 			if (value != null) {
421 				keys.put("extra_" + key, key);
422 				vars.put("extra_" + key, value);
423 			}
424 		}
425 		// Do not include the refresh token (even if there is one)
426 		return append(authorizationRequest.getRedirectUri(), vars, keys, true);
427 	}
428 
429 	private String generateCode(AuthorizationRequest authorizationRequest, Authentication authentication)
430 			throws AuthenticationException {
431 
432 		try {
433 
434 			OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest);
435 
436 			OAuth2Authentication combinedAuth = new OAuth2Authentication(storedOAuth2Request, authentication);
437 			String code = authorizationCodeServices.createAuthorizationCode(combinedAuth);
438 
439 			return code;
440 
441 		}
442 		catch (OAuth2Exception e) {
443 
444 			if (authorizationRequest.getState() != null) {
445 				e.addAdditionalInformation("state", authorizationRequest.getState());
446 			}
447 
448 			throw e;
449 
450 		}
451 	}
452 
453 	private String getSuccessfulRedirect(AuthorizationRequest authorizationRequest, String authorizationCode) {
454 
455 		if (authorizationCode == null) {
456 			throw new IllegalStateException("No authorization code found in the current request scope.");
457 		}
458 
459 		Map<String, String> query = new LinkedHashMap<String, String>();
460 		query.put("code", authorizationCode);
461 
462 		String state = authorizationRequest.getState();
463 		if (state != null) {
464 			query.put("state", state);
465 		}
466 
467 		return append(authorizationRequest.getRedirectUri(), query, false);
468 	}
469 
470 	private String getUnsuccessfulRedirect(AuthorizationRequest authorizationRequest, OAuth2Exception failure,
471 			boolean fragment) {
472 
473 		if (authorizationRequest == null || authorizationRequest.getRedirectUri() == null) {
474 			// we have no redirect for the user. very sad.
475 			throw new UnapprovedClientAuthenticationException("Authorization failure, and no redirect URI.", failure);
476 		}
477 
478 		Map<String, String> query = new LinkedHashMap<String, String>();
479 
480 		query.put("error", failure.getOAuth2ErrorCode());
481 		query.put("error_description", failure.getMessage());
482 
483 		if (authorizationRequest.getState() != null) {
484 			query.put("state", authorizationRequest.getState());
485 		}
486 
487 		if (failure.getAdditionalInformation() != null) {
488 			for (Map.Entry<String, String> additionalInfo : failure.getAdditionalInformation().entrySet()) {
489 				query.put(additionalInfo.getKey(), additionalInfo.getValue());
490 			}
491 		}
492 
493 		return append(authorizationRequest.getRedirectUri(), query, fragment);
494 
495 	}
496 
497 	private String append(String base, Map<String, ?> query, boolean fragment) {
498 		return append(base, query, null, fragment);
499 	}
500 
501 	private String append(String base, Map<String, ?> query, Map<String, String> keys, boolean fragment) {
502 
503 		UriComponentsBuilder template = UriComponentsBuilder.newInstance();
504 		UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base);
505 		URI redirectUri;
506 		try {
507 			// assume it's encoded to start with (if it came in over the wire)
508 			redirectUri = builder.build(true).toUri();
509 		}
510 		catch (Exception e) {
511 			// ... but allow client registrations to contain hard-coded non-encoded values
512 			redirectUri = builder.build().toUri();
513 			builder = UriComponentsBuilder.fromUri(redirectUri);
514 		}
515 		template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost())
516 				.userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath());
517 
518 		if (fragment) {
519 			StringBuilder values = new StringBuilder();
520 			if (redirectUri.getFragment() != null) {
521 				String append = redirectUri.getFragment();
522 				values.append(append);
523 			}
524 			for (String key : query.keySet()) {
525 				if (values.length() > 0) {
526 					values.append("&");
527 				}
528 				String name = key;
529 				if (keys != null && keys.containsKey(key)) {
530 					name = keys.get(key);
531 				}
532 				values.append(name + "={" + key + "}");
533 			}
534 			if (values.length() > 0) {
535 				template.fragment(values.toString());
536 			}
537 			UriComponents encoded = template.build().expand(query).encode();
538 			builder.fragment(encoded.getFragment());
539 		}
540 		else {
541 			for (String key : query.keySet()) {
542 				String name = key;
543 				if (keys != null && keys.containsKey(key)) {
544 					name = keys.get(key);
545 				}
546 				template.queryParam(name, "{" + key + "}");
547 			}
548 			template.fragment(redirectUri.getFragment());
549 			UriComponents encoded = template.build().expand(query).encode();
550 			builder.query(encoded.getQuery());
551 		}
552 
553 		return builder.build().toUriString();
554 
555 	}
556 
557 	public void setUserApprovalPage(String userApprovalPage) {
558 		this.userApprovalPage = userApprovalPage;
559 	}
560 
561 	public void setAuthorizationCodeServices(AuthorizationCodeServices authorizationCodeServices) {
562 		this.authorizationCodeServices = authorizationCodeServices;
563 	}
564 
565 	public void setRedirectResolver(RedirectResolver redirectResolver) {
566 		this.redirectResolver = redirectResolver;
567 	}
568 
569 	public void setUserApprovalHandler(UserApprovalHandler userApprovalHandler) {
570 		this.userApprovalHandler = userApprovalHandler;
571 	}
572 
573 	public void setOAuth2RequestValidator(OAuth2RequestValidator oauth2RequestValidator) {
574 		this.oauth2RequestValidator = oauth2RequestValidator;
575 	}
576 
577 	@SuppressWarnings("deprecation")
578 	public void setImplicitGrantService(
579 			org.springframework.security.oauth2.provider.implicit.ImplicitGrantService implicitGrantService) {
580 	}
581 
582 	@ExceptionHandler(ClientRegistrationException.class)
583 	public ModelAndView handleClientRegistrationException(Exception e, ServletWebRequest webRequest) throws Exception {
584 		logger.info("Handling ClientRegistrationException error: " + e.getMessage());
585 		return handleException(new BadClientCredentialsException(), webRequest);
586 	}
587 
588 	@ExceptionHandler(OAuth2Exception.class)
589 	public ModelAndView handleOAuth2Exception(OAuth2Exception e, ServletWebRequest webRequest) throws Exception {
590 		logger.info("Handling OAuth2 error: " + e.getSummary());
591 		return handleException(e, webRequest);
592 	}
593 
594 	@ExceptionHandler(HttpSessionRequiredException.class)
595 	public ModelAndView handleHttpSessionRequiredException(HttpSessionRequiredException e, ServletWebRequest webRequest)
596 			throws Exception {
597 		logger.info("Handling Session required error: " + e.getMessage());
598 		return handleException(new AccessDeniedException("Could not obtain authorization request from session", e),
599 				webRequest);
600 	}
601 
602 	private ModelAndView handleException(Exception e, ServletWebRequest webRequest) throws Exception {
603 
604 		ResponseEntity<OAuth2Exception> translate = getExceptionTranslator().translate(e);
605 		webRequest.getResponse().setStatus(translate.getStatusCode().value());
606 
607 		if (e instanceof ClientAuthenticationException || e instanceof RedirectMismatchException) {
608 			return new ModelAndView(errorPage, Collections.singletonMap("error", translate.getBody()));
609 		}
610 
611 		AuthorizationRequest authorizationRequest = null;
612 		try {
613 			authorizationRequest = getAuthorizationRequestForError(webRequest);
614 			String requestedRedirectParam = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
615 			String requestedRedirect = redirectResolver.resolveRedirect(requestedRedirectParam,
616 					getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId()));
617 			authorizationRequest.setRedirectUri(requestedRedirect);
618 			String redirect = getUnsuccessfulRedirect(authorizationRequest, translate.getBody(), authorizationRequest
619 					.getResponseTypes().contains("token"));
620 			RedirectView redirectView = new RedirectView(redirect, false, true, false);
621 			redirectView.setStatusCode(HttpStatus.SEE_OTHER);
622 			return new ModelAndView(redirectView);
623 		}
624 		catch (OAuth2Exception ex) {
625 			// If an AuthorizationRequest cannot be created from the incoming parameters it must be
626 			// an error. OAuth2Exception can be handled this way. Other exceptions will generate a standard 500
627 			// response.
628 			return new ModelAndView(errorPage, Collections.singletonMap("error", translate.getBody()));
629 		}
630 
631 	}
632 
633 	private AuthorizationRequest getAuthorizationRequestForError(ServletWebRequest webRequest) {
634 
635 		// If it's already there then we are in the approveOrDeny phase and we can use the saved request
636 		AuthorizationRequest authorizationRequest = (AuthorizationRequest) sessionAttributeStore.retrieveAttribute(
637 				webRequest, AUTHORIZATION_REQUEST_ATTR_NAME);
638 		if (authorizationRequest != null) {
639 			return authorizationRequest;
640 		}
641 
642 		Map<String, String> parameters = new HashMap<String, String>();
643 		Map<String, String[]> map = webRequest.getParameterMap();
644 		for (String key : map.keySet()) {
645 			String[] values = map.get(key);
646 			if (values != null && values.length > 0) {
647 				parameters.put(key, values[0]);
648 			}
649 		}
650 
651 		try {
652 			return getOAuth2RequestFactory().createAuthorizationRequest(parameters);
653 		}
654 		catch (Exception e) {
655 			return getDefaultOAuth2RequestFactory().createAuthorizationRequest(parameters);
656 		}
657 
658 	}
659 	
660 	private void setCacheControlHeaders() {
661 		ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
662 		if (servletRequestAttributes != null) {
663 			HttpServletResponse servletResponse = servletRequestAttributes.getResponse();
664 			servletResponse.setHeader(HttpHeaders.CACHE_CONTROL, "no-store");
665 			servletResponse.setHeader(HttpHeaders.PRAGMA, "no-cache");
666 		}
667 	}
668 }