View Javadoc
1   /*
2    * Copyright 2010-2012 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.client.filter;
18  
19  import java.io.IOException;
20  
21  import javax.servlet.FilterChain;
22  import javax.servlet.ServletException;
23  import javax.servlet.http.HttpServletRequest;
24  import javax.servlet.http.HttpServletResponse;
25  
26  import org.springframework.context.ApplicationEvent;
27  import org.springframework.context.ApplicationEventPublisher;
28  import org.springframework.security.authentication.AuthenticationDetailsSource;
29  import org.springframework.security.authentication.AuthenticationManager;
30  import org.springframework.security.authentication.BadCredentialsException;
31  import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
32  import org.springframework.security.core.Authentication;
33  import org.springframework.security.core.AuthenticationException;
34  import org.springframework.security.oauth2.client.OAuth2RestOperations;
35  import org.springframework.security.oauth2.client.http.AccessTokenRequiredException;
36  import org.springframework.security.oauth2.common.OAuth2AccessToken;
37  import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
38  import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
39  import org.springframework.security.oauth2.provider.OAuth2Authentication;
40  import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
41  import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetailsSource;
42  import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
43  import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
44  import org.springframework.util.Assert;
45  
46  /**
47   * An OAuth2 client filter that can be used to acquire an OAuth2 access token from an authorization server, and load an
48   * authentication object into the SecurityContext
49   * 
50   * @author Vidya Valmikinathan
51   * 
52   */
53  public class OAuth2ClientAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
54  
55  	public OAuth2RestOperations restTemplate;
56  
57  	private ResourceServerTokenServices tokenServices;
58  
59  	private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();
60  
61  	private ApplicationEventPublisher eventPublisher;
62  
63  	/**
64  	 * Reference to a CheckTokenServices that can validate an OAuth2AccessToken
65  	 * 
66  	 * @param tokenServices
67  	 */
68  	public void setTokenServices(ResourceServerTokenServices tokenServices) {
69  		this.tokenServices = tokenServices;
70  	}
71  
72  	/**
73  	 * A rest template to be used to obtain an access token.
74  	 * 
75  	 * @param restTemplate a rest template
76  	 */
77  	public void setRestTemplate(OAuth2RestOperations restTemplate) {
78  		this.restTemplate = restTemplate;
79  	}
80  	
81  	@Override
82  	public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
83  		this.eventPublisher = eventPublisher;
84  		super.setApplicationEventPublisher(eventPublisher);
85  	}
86  	
87  	public OAuth2ClientAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
88  		super(defaultFilterProcessesUrl);
89  		setAuthenticationManager(new NoopAuthenticationManager());
90  		setAuthenticationDetailsSource(authenticationDetailsSource);
91  	}
92  
93  	@Override
94  	public void afterPropertiesSet() {
95  		Assert.state(restTemplate != null, "Supply a rest-template");
96  		super.afterPropertiesSet();
97  	}
98  
99  	@Override
100 	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
101 			throws AuthenticationException, IOException, ServletException {
102 
103 		OAuth2AccessToken accessToken;
104 		try {
105 			accessToken = restTemplate.getAccessToken();
106 		} catch (OAuth2Exception e) {
107 			BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
108 			publish(new OAuth2AuthenticationFailureEvent(bad));
109 			throw bad;			
110 		}
111 		try {
112 			OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue());
113 			if (authenticationDetailsSource!=null) {
114 				request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
115 				request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
116 				result.setDetails(authenticationDetailsSource.buildDetails(request));
117 			}
118 			publish(new AuthenticationSuccessEvent(result));
119 			return result;
120 		}
121 		catch (InvalidTokenException e) {
122 			BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
123 			publish(new OAuth2AuthenticationFailureEvent(bad));
124 			throw bad;			
125 		}
126 
127 	}
128 
129 	private void publish(ApplicationEvent event) {
130 		if (eventPublisher!=null) {
131 			eventPublisher.publishEvent(event);
132 		}
133 	}
134 	
135 	@Override
136 	protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
137 			FilterChain chain, Authentication authResult) throws IOException, ServletException {
138 		super.successfulAuthentication(request, response, chain, authResult);
139 		// Nearly a no-op, but if there is a ClientTokenServices then the token will now be stored
140 		restTemplate.getAccessToken();
141 	}
142 
143 	@Override
144 	protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
145 			AuthenticationException failed) throws IOException, ServletException {
146 		if (failed instanceof AccessTokenRequiredException) {
147 			// Need to force a redirect via the OAuth client filter, so rethrow here
148 			throw failed;
149 		}
150 		else {
151 			// If the exception is not a Spring Security exception this will result in a default error page
152 			super.unsuccessfulAuthentication(request, response, failed);
153 		}
154 	}
155 	
156 	private static class NoopAuthenticationManager implements AuthenticationManager {
157 
158 		@Override
159 		public Authentication authenticate(Authentication authentication)
160 				throws AuthenticationException {
161 			throw new UnsupportedOperationException("No authentication should be done with this AuthenticationManager");
162 		}
163 		
164 	}
165 
166 }