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
5    * use this file except in compliance with the License. You may obtain a copy of
6    * 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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package org.springframework.security.oauth2.config.annotation.web.configurers;
17  
18  import java.util.ArrayList;
19  import java.util.Collections;
20  import java.util.List;
21  
22  import javax.servlet.Filter;
23  
24  import org.springframework.http.MediaType;
25  import org.springframework.security.authentication.AuthenticationManager;
26  import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
27  import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
28  import org.springframework.security.config.annotation.web.builders.HttpSecurity;
29  import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
30  import org.springframework.security.crypto.password.PasswordEncoder;
31  import org.springframework.security.oauth2.provider.ClientDetailsService;
32  import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter;
33  import org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService;
34  import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping;
35  import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
36  import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
37  import org.springframework.security.web.AuthenticationEntryPoint;
38  import org.springframework.security.web.DefaultSecurityFilterChain;
39  import org.springframework.security.web.access.AccessDeniedHandler;
40  import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
41  import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
42  import org.springframework.security.web.context.NullSecurityContextRepository;
43  import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
44  import org.springframework.util.Assert;
45  import org.springframework.util.StringUtils;
46  import org.springframework.web.accept.ContentNegotiationStrategy;
47  import org.springframework.web.accept.HeaderContentNegotiationStrategy;
48  
49  /**
50   * 
51   * @author Rob Winch
52   * @author Dave Syer
53   * @since 2.0
54   */
55  public final class AuthorizationServerSecurityConfigurer extends
56  		SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
57  
58  	private AuthenticationEntryPoint authenticationEntryPoint;
59  
60  	private AccessDeniedHandler accessDeniedHandler = new OAuth2AccessDeniedHandler();
61  
62  	private PasswordEncoder passwordEncoder; // for client secrets
63  
64  	private String realm = "oauth2/client";
65  
66  	private boolean allowFormAuthenticationForClients = false;
67  
68  	private String tokenKeyAccess = "denyAll()";
69  
70  	private String checkTokenAccess = "denyAll()";
71  
72  	private boolean sslOnly = false;
73  
74  	/**
75  	 * Custom authentication filters for the TokenEndpoint. Filters will be set upstream of the default
76  	 * BasicAuthenticationFilter.
77  	 */
78  	private List<Filter> tokenEndpointAuthenticationFilters = new ArrayList<Filter>();
79  
80  	public AuthorizationServerSecurityConfigurer sslOnly() {
81  		this.sslOnly = true;
82  		return this;
83  	}
84  
85  	public AuthorizationServerSecurityConfigurer allowFormAuthenticationForClients() {
86  		this.allowFormAuthenticationForClients = true;
87  		return this;
88  	}
89  
90  	public AuthorizationServerSecurityConfigurer realm(String realm) {
91  		this.realm = realm;
92  		return this;
93  	}
94  
95  	public AuthorizationServerSecurityConfigurer passwordEncoder(PasswordEncoder passwordEncoder) {
96  		this.passwordEncoder = passwordEncoder;
97  		return this;
98  	}
99  
100 	public AuthorizationServerSecurityConfigurer authenticationEntryPoint(
101 			AuthenticationEntryPoint authenticationEntryPoint) {
102 		this.authenticationEntryPoint = authenticationEntryPoint;
103 		return this;
104 	}
105 
106 	public AuthorizationServerSecurityConfigurer accessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
107 		this.accessDeniedHandler = accessDeniedHandler;
108 		return this;
109 	}
110 
111 	public AuthorizationServerSecurityConfigurer tokenKeyAccess(String tokenKeyAccess) {
112 		this.tokenKeyAccess = tokenKeyAccess;
113 		return this;
114 	}
115 
116 	public AuthorizationServerSecurityConfigurer checkTokenAccess(String checkTokenAccess) {
117 		this.checkTokenAccess = checkTokenAccess;
118 		return this;
119 	}
120 
121 	public String getTokenKeyAccess() {
122 		return tokenKeyAccess;
123 	}
124 
125 	public String getCheckTokenAccess() {
126 		return checkTokenAccess;
127 	}
128 
129 	@Override
130 	public void init(HttpSecurity http) throws Exception {
131 
132 		registerDefaultAuthenticationEntryPoint(http);
133 		if (passwordEncoder != null) {
134 			ClientDetailsUserDetailsService clientDetailsUserDetailsService = new ClientDetailsUserDetailsService(clientDetailsService());
135 			clientDetailsUserDetailsService.setPasswordEncoder(passwordEncoder());
136 			http.getSharedObject(AuthenticationManagerBuilder.class)
137 					.userDetailsService(clientDetailsUserDetailsService)
138 					.passwordEncoder(passwordEncoder());
139 		}
140 		else {
141 			http.userDetailsService(new ClientDetailsUserDetailsService(clientDetailsService()));
142 		}
143 		http.securityContext().securityContextRepository(new NullSecurityContextRepository()).and().csrf().disable()
144 				.httpBasic().authenticationEntryPoint(this.authenticationEntryPoint).realmName(realm);
145 		if (sslOnly) {
146 			http.requiresChannel().anyRequest().requiresSecure();
147 		}
148 	}
149 
150 	private PasswordEncoder passwordEncoder() {
151 		return new PasswordEncoder() {
152 
153 			@Override
154 			public boolean matches(CharSequence rawPassword, String encodedPassword) {
155 				return StringUtils.hasText(encodedPassword) ? passwordEncoder.matches(rawPassword, encodedPassword)
156 						: true;
157 			}
158 
159 			@Override
160 			public String encode(CharSequence rawPassword) {
161 				return passwordEncoder.encode(rawPassword);
162 			}
163 		};
164 	}
165 
166 	@SuppressWarnings("unchecked")
167 	private void registerDefaultAuthenticationEntryPoint(HttpSecurity http) {
168 		ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling = http
169 				.getConfigurer(ExceptionHandlingConfigurer.class);
170 		if (exceptionHandling == null) {
171 			return;
172 		}
173 		if (authenticationEntryPoint==null) {
174 			BasicAuthenticationEntryPoint basicEntryPoint = new BasicAuthenticationEntryPoint();
175 			basicEntryPoint.setRealmName(realm);
176 			authenticationEntryPoint = basicEntryPoint;
177 		}
178 		ContentNegotiationStrategy contentNegotiationStrategy = http.getSharedObject(ContentNegotiationStrategy.class);
179 		if (contentNegotiationStrategy == null) {
180 			contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
181 		}
182 		MediaTypeRequestMatcher preferredMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy,
183 				MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON,
184 				MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.MULTIPART_FORM_DATA,
185 				MediaType.TEXT_XML);
186 		preferredMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
187 		exceptionHandling.defaultAuthenticationEntryPointFor(postProcess(authenticationEntryPoint), preferredMatcher);
188 	}
189 
190 	@Override
191 	public void configure(HttpSecurity http) throws Exception {
192 		
193 		// ensure this is initialized
194 		frameworkEndpointHandlerMapping();
195 		if (allowFormAuthenticationForClients) {
196 			clientCredentialsTokenEndpointFilter(http);
197 		}
198 
199 		for (Filter filter : tokenEndpointAuthenticationFilters) {
200 			http.addFilterBefore(filter, BasicAuthenticationFilter.class);
201 		}
202 
203 		http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
204 	}
205 
206 	private ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter(HttpSecurity http) {
207 		ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter = new ClientCredentialsTokenEndpointFilter(
208 				frameworkEndpointHandlerMapping().getServletPath("/oauth/token"));
209 		clientCredentialsTokenEndpointFilter
210 				.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
211 		OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
212 		authenticationEntryPoint.setTypeName("Form");
213 		authenticationEntryPoint.setRealmName(realm);
214 		clientCredentialsTokenEndpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
215 		clientCredentialsTokenEndpointFilter = postProcess(clientCredentialsTokenEndpointFilter);
216 		http.addFilterBefore(clientCredentialsTokenEndpointFilter, BasicAuthenticationFilter.class);
217 		return clientCredentialsTokenEndpointFilter;
218 	}
219 
220 	private ClientDetailsService clientDetailsService() {
221 		return getBuilder().getSharedObject(ClientDetailsService.class);
222 	}
223 
224 	private FrameworkEndpointHandlerMapping frameworkEndpointHandlerMapping() {
225 		return getBuilder().getSharedObject(FrameworkEndpointHandlerMapping.class);
226 	}
227 
228 	/**
229 	 * Adds a new custom authentication filter for the TokenEndpoint. Filters will be set upstream of the default
230 	 * BasicAuthenticationFilter.
231 	 * 
232 	 * @param filter
233 	 */
234 	public void addTokenEndpointAuthenticationFilter(Filter filter) {
235 		this.tokenEndpointAuthenticationFilters.add(filter);
236 	}
237 
238 	/**
239 	 * Sets a new list of custom authentication filters for the TokenEndpoint. Filters will be set upstream of the
240 	 * default BasicAuthenticationFilter.
241 	 * 
242 	 * @param filters The authentication filters to set.
243 	 */
244 	public void tokenEndpointAuthenticationFilters(List<Filter> filters) {
245 		Assert.notNull(filters, "Custom authentication filter list must not be null");
246 		this.tokenEndpointAuthenticationFilters = new ArrayList<Filter>(filters);
247 	}
248 }