View Javadoc
1   /*
2    * Copyright 2006-2011 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.authentication;
14  
15  import java.io.IOException;
16  
17  import javax.servlet.Filter;
18  import javax.servlet.FilterChain;
19  import javax.servlet.FilterConfig;
20  import javax.servlet.ServletException;
21  import javax.servlet.ServletRequest;
22  import javax.servlet.ServletResponse;
23  import javax.servlet.http.HttpServletRequest;
24  import javax.servlet.http.HttpServletResponse;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.springframework.beans.factory.InitializingBean;
29  import org.springframework.security.authentication.AbstractAuthenticationToken;
30  import org.springframework.security.authentication.AnonymousAuthenticationToken;
31  import org.springframework.security.authentication.AuthenticationDetailsSource;
32  import org.springframework.security.authentication.AuthenticationEventPublisher;
33  import org.springframework.security.authentication.AuthenticationManager;
34  import org.springframework.security.authentication.BadCredentialsException;
35  import org.springframework.security.authentication.InsufficientAuthenticationException;
36  import org.springframework.security.core.Authentication;
37  import org.springframework.security.core.AuthenticationException;
38  import org.springframework.security.core.context.SecurityContextHolder;
39  import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
40  import org.springframework.security.oauth2.provider.OAuth2Authentication;
41  import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
42  import org.springframework.security.web.AuthenticationEntryPoint;
43  import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
44  import org.springframework.util.Assert;
45  
46  /**
47   * A pre-authentication filter for OAuth2 protected resources. Extracts an OAuth2 token from the incoming request and
48   * uses it to populate the Spring Security context with an {@link OAuth2Authentication} (if used in conjunction with an
49   * {@link OAuth2AuthenticationManager}).
50   * 
51   * @author Dave Syer
52   * 
53   */
54  public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean {
55  
56  	private final static Log logger = LogFactory.getLog(OAuth2AuthenticationProcessingFilter.class);
57  
58  	private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
59  
60  	private AuthenticationManager authenticationManager;
61  
62  	private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();
63  
64  	private TokenExtractor tokenExtractor = new BearerTokenExtractor();
65  
66  	private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
67  
68  	private boolean stateless = true;
69  
70  	/**
71  	 * Flag to say that this filter guards stateless resources (default true). Set this to true if the only way the
72  	 * resource can be accessed is with a token. If false then an incoming cookie can populate the security context and
73  	 * allow access to a caller that isn't an OAuth2 client.
74  	 * 
75  	 * @param stateless the flag to set (default true)
76  	 */
77  	public void setStateless(boolean stateless) {
78  		this.stateless = stateless;
79  	}
80  
81  	/**
82  	 * @param authenticationEntryPoint the authentication entry point to set
83  	 */
84  	public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
85  		this.authenticationEntryPoint = authenticationEntryPoint;
86  	}
87  
88  	/**
89  	 * @param authenticationManager the authentication manager to set (mandatory with no default)
90  	 */
91  	public void setAuthenticationManager(AuthenticationManager authenticationManager) {
92  		this.authenticationManager = authenticationManager;
93  	}
94  
95  	/**
96  	 * @param tokenExtractor the tokenExtractor to set
97  	 */
98  	public void setTokenExtractor(TokenExtractor tokenExtractor) {
99  		this.tokenExtractor = tokenExtractor;
100 	}
101 
102 	/**
103 	 * @param eventPublisher the event publisher to set
104 	 */
105 	public void setAuthenticationEventPublisher(AuthenticationEventPublisher eventPublisher) {
106 		this.eventPublisher = eventPublisher;
107 	}
108 
109 	/**
110 	 * @param authenticationDetailsSource The AuthenticationDetailsSource to use
111 	 */
112 	public void setAuthenticationDetailsSource(
113 			AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
114 		Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
115 		this.authenticationDetailsSource = authenticationDetailsSource;
116 	}
117 
118 	public void afterPropertiesSet() {
119 		Assert.state(authenticationManager != null, "AuthenticationManager is required");
120 	}
121 
122 	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
123 			ServletException {
124 
125 		final boolean debug = logger.isDebugEnabled();
126 		final HttpServletRequest request = (HttpServletRequest) req;
127 		final HttpServletResponse response = (HttpServletResponse) res;
128 
129 		try {
130 
131 			Authentication authentication = tokenExtractor.extract(request);
132 			
133 			if (authentication == null) {
134 				if (stateless && isAuthenticated()) {
135 					if (debug) {
136 						logger.debug("Clearing security context.");
137 					}
138 					SecurityContextHolder.clearContext();
139 				}
140 				if (debug) {
141 					logger.debug("No token in request, will continue chain.");
142 				}
143 			}
144 			else {
145 				request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
146 				if (authentication instanceof AbstractAuthenticationToken) {
147 					AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
148 					needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
149 				}
150 				Authentication authResult = authenticationManager.authenticate(authentication);
151 
152 				if (debug) {
153 					logger.debug("Authentication success: " + authResult);
154 				}
155 
156 				eventPublisher.publishAuthenticationSuccess(authResult);
157 				SecurityContextHolder.getContext().setAuthentication(authResult);
158 
159 			}
160 		}
161 		catch (OAuth2Exception failed) {
162 			SecurityContextHolder.clearContext();
163 
164 			if (debug) {
165 				logger.debug("Authentication request failed: " + failed);
166 			}
167 			eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
168 					new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
169 
170 			authenticationEntryPoint.commence(request, response,
171 					new InsufficientAuthenticationException(failed.getMessage(), failed));
172 
173 			return;
174 		}
175 
176 		chain.doFilter(request, response);
177 	}
178 
179 	private boolean isAuthenticated() {
180 		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
181 		if (authentication == null || authentication instanceof AnonymousAuthenticationToken) {
182 			return false;
183 		}
184 		return true;
185 	}
186 
187 	public void init(FilterConfig filterConfig) throws ServletException {
188 	}
189 
190 	public void destroy() {
191 	}
192 
193 	private static final class NullEventPublisher implements AuthenticationEventPublisher {
194 		public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
195 		}
196 
197 		public void publishAuthenticationSuccess(Authentication authentication) {
198 		}
199 	}
200 
201 }