View Javadoc
1   package org.springframework.security.oauth2.client.token.grant.implicit;
2   
3   import java.io.IOException;
4   import java.net.URI;
5   import java.util.Collections;
6   import java.util.Iterator;
7   import java.util.List;
8   
9   import org.springframework.http.HttpHeaders;
10  import org.springframework.http.client.ClientHttpResponse;
11  import org.springframework.security.access.AccessDeniedException;
12  import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException;
13  import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
14  import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
15  import org.springframework.security.oauth2.client.token.AccessTokenProvider;
16  import org.springframework.security.oauth2.client.token.AccessTokenRequest;
17  import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest;
18  import org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport;
19  import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
20  import org.springframework.security.oauth2.common.OAuth2AccessToken;
21  import org.springframework.security.oauth2.common.OAuth2RefreshToken;
22  import org.springframework.security.oauth2.common.util.OAuth2Utils;
23  import org.springframework.util.LinkedMultiValueMap;
24  import org.springframework.util.MultiValueMap;
25  import org.springframework.web.client.ResponseExtractor;
26  
27  /**
28   * Provider for obtaining an oauth2 access token by using implicit grant. Normally the implicit grant is used by script
29   * clients in a browser or device, but it can also be useful for native clients generally, so if those clients were
30   * written in Java this would be a nice convenience. Web application clients are also a possiblity, although the
31   * authorization code grant type is probably more common there, and requires no special customizations on the
32   * authorization server. Callers add any additional form parameters they need to the {@link DefaultAccessTokenRequest}
33   * and these will be passed onto the authorization endpoint on the server. The server then has to interpret those
34   * parameters, together with any other information available (e.g. from a cookie), and decide if a user can be
35   * authenticated and if the user has approved the grant of the access token. Only if those two conditions are met should
36   * an access token be available through this provider.
37   * 
38   * @author Dave Syer
39   */
40  public class ImplicitAccessTokenProvider extends OAuth2AccessTokenSupport implements AccessTokenProvider {
41  
42  	public boolean supportsResource(OAuth2ProtectedResourceDetails resource) {
43  		return resource instanceof ImplicitResourceDetails && "implicit".equals(resource.getGrantType());
44  	}
45  
46  	public boolean supportsRefresh(OAuth2ProtectedResourceDetails resource) {
47  		return false;
48  	}
49  
50  	public OAuth2AccessToken refreshAccessToken(OAuth2ProtectedResourceDetails resource,
51  			OAuth2RefreshToken refreshToken, AccessTokenRequest request) throws UserRedirectRequiredException {
52  		return null;
53  	}
54  
55  	public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
56  			throws UserRedirectRequiredException, AccessDeniedException, OAuth2AccessDeniedException {
57  
58  		ImplicitResourceDetails resource = (ImplicitResourceDetails) details;
59  		try {
60  			// We can assume here that the request contains all the parameters needed for authentication etc.
61  			OAuth2AccessToken token = retrieveToken(request,
62  					resource, getParametersForTokenRequest(resource, request), getHeadersForTokenRequest(request));
63  			if (token==null) {
64  				// Probably an authenticated request, but approval is required.  TODO: prompt somehow?
65  				throw new UserRedirectRequiredException(resource.getUserAuthorizationUri(), request.toSingleValueMap());				
66  			}
67  			return token;
68  		}
69  		catch (UserRedirectRequiredException e) {
70  			// ... but if it doesn't then capture the request parameters for the redirect
71  			throw new UserRedirectRequiredException(e.getRedirectUri(), request.toSingleValueMap());
72  		}
73  
74  	}
75  
76  	@Override
77  	protected ResponseExtractor<OAuth2AccessToken> getResponseExtractor() {
78  		return new ImplicitResponseExtractor();
79  	}
80  
81  	private HttpHeaders getHeadersForTokenRequest(AccessTokenRequest request) {
82  		HttpHeaders headers = new HttpHeaders();
83  		headers.putAll(request.getHeaders());
84  		if (request.getCookie() != null) {
85  			headers.set("Cookie", request.getCookie());
86  		}
87  		return headers;
88  	}
89  
90  	private MultiValueMap<String, String> getParametersForTokenRequest(ImplicitResourceDetails resource,
91  			AccessTokenRequest request) {
92  
93  		MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
94  		form.set("response_type", "token");
95  		form.set("client_id", resource.getClientId());
96  		
97  		if (resource.isScoped()) {
98  
99  			StringBuilder builder = new StringBuilder();
100 			List<String> scope = resource.getScope();
101 
102 			if (scope != null) {
103 				Iterator<String> scopeIt = scope.iterator();
104 				while (scopeIt.hasNext()) {
105 					builder.append(scopeIt.next());
106 					if (scopeIt.hasNext()) {
107 						builder.append(' ');
108 					}
109 				}
110 			}
111 
112 			form.set("scope", builder.toString());
113 		}
114 
115 		for (String key : request.keySet()) {
116 			form.put(key, request.get(key));
117 		}
118 
119 		String redirectUri = resource.getRedirectUri(request);
120 		if (redirectUri == null) {
121 			throw new IllegalStateException("No redirect URI available in request");
122 		}
123 		form.set("redirect_uri", redirectUri);
124 
125 		return form;
126 
127 	}
128 
129 	private final class ImplicitResponseExtractor implements ResponseExtractor<OAuth2AccessToken> {
130 		public OAuth2AccessToken extractData(ClientHttpResponse response) throws IOException {
131 			// TODO: this should actually be a 401 if the request asked for JSON
132 			URI location = response.getHeaders().getLocation();
133 			if (location == null) {
134 				return null;
135 			}
136 			String fragment = location.getFragment();
137 			OAuth2AccessToken accessToken = DefaultOAuth2AccessToken.valueOf(OAuth2Utils.extractMap(fragment));
138 			if (accessToken.getValue() == null) {
139 				throw new UserRedirectRequiredException(location.toString(), Collections.<String, String> emptyMap());
140 			}
141 
142 			return accessToken;
143 		}
144 	}
145 
146 }