View Javadoc
1   /*
2    * Copyright 2012-2013 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  
17  package org.springframework.security.oauth2.provider.endpoint;
18  
19  import java.io.IOException;
20  import java.util.HashMap;
21  import java.util.Map;
22  import java.util.Set;
23  
24  import javax.servlet.Filter;
25  import javax.servlet.FilterChain;
26  import javax.servlet.FilterConfig;
27  import javax.servlet.ServletException;
28  import javax.servlet.ServletRequest;
29  import javax.servlet.ServletResponse;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.springframework.security.authentication.AuthenticationDetailsSource;
36  import org.springframework.security.authentication.AuthenticationManager;
37  import org.springframework.security.authentication.BadCredentialsException;
38  import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
39  import org.springframework.security.core.Authentication;
40  import org.springframework.security.core.AuthenticationException;
41  import org.springframework.security.core.context.SecurityContext;
42  import org.springframework.security.core.context.SecurityContextHolder;
43  import org.springframework.security.oauth2.common.util.OAuth2Utils;
44  import org.springframework.security.oauth2.provider.AuthorizationRequest;
45  import org.springframework.security.oauth2.provider.OAuth2Authentication;
46  import org.springframework.security.oauth2.provider.OAuth2Request;
47  import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
48  import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
49  import org.springframework.security.web.AuthenticationEntryPoint;
50  import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
51  import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
52  
53  /**
54   * <p>
55   * An optional authentication filter for the {@link TokenEndpoint}. It sits downstream of another filter (usually
56   * {@link BasicAuthenticationFilter}) for the client, and creates an {@link OAuth2Authentication} for the Spring
57   * {@link SecurityContext} if the request also contains user credentials, e.g. as typically would be the case in a
58   * password grant. This filter is only required if the TokenEndpoint (or one of it's dependencies) needs to know about
59   * the authenticated user. In a vanilla password grant this <b>isn't</b> normally necessary because the token granter
60   * will also authenticate the user.
61   * </p>
62   * 
63   * <p>
64   * If this filter is used the Spring Security context will contain an OAuth2Authentication encapsulating (as the
65   * authorization request) the form parameters coming into the filter and the client id from the already authenticated
66   * client authentication, and the authenticated user token extracted from the request and validated using the
67   * authentication manager.
68   * </p>
69   * 
70   * @author Dave Syer
71   * 
72   */
73  public class TokenEndpointAuthenticationFilter implements Filter {
74  
75  	private static final Log logger = LogFactory.getLog(TokenEndpointAuthenticationFilter.class);
76  
77  	private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
78  
79  	private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
80  
81  	private final AuthenticationManager authenticationManager;
82  	
83  	private final OAuth2RequestFactory oAuth2RequestFactory;
84  
85  	/**
86  	 * @param authenticationManager an AuthenticationManager for the incoming request
87  	 */
88  	public TokenEndpointAuthenticationFilter(AuthenticationManager authenticationManager, OAuth2RequestFactory oAuth2RequestFactory) {
89  		super();
90  		this.authenticationManager = authenticationManager;
91  		this.oAuth2RequestFactory = oAuth2RequestFactory;
92  	}
93  
94  	/**
95  	 * An authentication entry point that can handle unsuccessful authentication. Defaults to an
96  	 * {@link OAuth2AuthenticationEntryPoint}.
97  	 * 
98  	 * @param authenticationEntryPoint the authenticationEntryPoint to set
99  	 */
100 	public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
101 		this.authenticationEntryPoint = authenticationEntryPoint;
102 	}
103 
104 	/**
105 	 * A source of authentication details for requests that result in authentication.
106 	 * 
107 	 * @param authenticationDetailsSource the authenticationDetailsSource to set
108 	 */
109 	public void setAuthenticationDetailsSource(
110 			AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
111 		this.authenticationDetailsSource = authenticationDetailsSource;
112 	}
113 
114 	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
115 			ServletException {
116 
117 		final boolean debug = logger.isDebugEnabled();
118 		final HttpServletRequest request = (HttpServletRequest) req;
119 		final HttpServletResponse response = (HttpServletResponse) res;
120 
121 		try {
122 			Authentication credentials = extractCredentials(request);
123 
124 			if (credentials != null) {
125 
126 				if (debug) {
127 					logger.debug("Authentication credentials found for '" + credentials.getName() + "'");
128 				}
129 
130 				Authentication authResult = authenticationManager.authenticate(credentials);
131 
132 				if (debug) {
133 					logger.debug("Authentication success: " + authResult.getName());
134 				}
135 
136 				Authentication clientAuth = SecurityContextHolder.getContext().getAuthentication();
137 				if (clientAuth == null) {
138 					throw new BadCredentialsException(
139 							"No client authentication found. Remember to put a filter upstream of the TokenEndpointAuthenticationFilter.");
140 				}
141 				
142 				Map<String, String> map = getSingleValueMap(request);
143 				map.put(OAuth2Utils.CLIENT_ID, clientAuth.getName());
144 				AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(map);
145 
146 				authorizationRequest.setScope(getScope(request));
147 				if (clientAuth.isAuthenticated()) {
148 					// Ensure the OAuth2Authentication is authenticated
149 					authorizationRequest.setApproved(true);
150 				}
151 
152 				OAuth2Request storedOAuth2Request = oAuth2RequestFactory.createOAuth2Request(authorizationRequest);
153 				
154 				SecurityContextHolder.getContext().setAuthentication(
155 						new OAuth2Authentication(storedOAuth2Request, authResult));
156 
157 				onSuccessfulAuthentication(request, response, authResult);
158 
159 			}
160 
161 		}
162 		catch (AuthenticationException failed) {
163 			SecurityContextHolder.clearContext();
164 
165 			if (debug) {
166 				logger.debug("Authentication request for failed: " + failed);
167 			}
168 
169 			onUnsuccessfulAuthentication(request, response, failed);
170 
171 			authenticationEntryPoint.commence(request, response, failed);
172 
173 			return;
174 		}
175 
176 		chain.doFilter(request, response);
177 	}
178 
179 	private Map<String, String> getSingleValueMap(HttpServletRequest request) {
180 		Map<String, String> map = new HashMap<String, String>();
181 		Map<String, String[]> parameters = request.getParameterMap();
182 		for (String key : parameters.keySet()) {
183 			String[] values = parameters.get(key);
184 			map.put(key, values != null && values.length > 0 ? values[0] : null);
185 		}
186 		return map;
187 	}
188 
189 	protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
190 			Authentication authResult) throws IOException {
191 	}
192 
193 	protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
194 			AuthenticationException failed) throws IOException {
195 	}
196 
197 	/**
198 	 * If the incoming request contains user credentials in headers or parameters then extract them here into an
199 	 * Authentication token that can be validated later. This implementation only recognises password grant requests and
200 	 * extracts the username and password.
201 	 * 
202 	 * @param request the incoming request, possibly with user credentials
203 	 * @return an authentication for validation (or null if there is no further authentication)
204 	 */
205 	protected Authentication extractCredentials(HttpServletRequest request) {
206 		String grantType = request.getParameter("grant_type");
207 		if (grantType != null && grantType.equals("password")) {
208 			UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
209 					request.getParameter("username"), request.getParameter("password"));
210 			result.setDetails(authenticationDetailsSource.buildDetails(request));
211 			return result;
212 		}
213 		return null;
214 	}
215 
216 	private Set<String> getScope(HttpServletRequest request) {
217 		return OAuth2Utils.parseParameterList(request.getParameter("scope"));
218 	}
219 	
220 	public void init(FilterConfig filterConfig) throws ServletException {
221 	}
222 
223 	public void destroy() {
224 	}
225 
226 }