-
Notifications
You must be signed in to change notification settings - Fork 5.9k
/
AbstractPreAuthenticatedProcessingFilter.java
executable file
·362 lines (316 loc) · 13.5 KB
/
AbstractPreAuthenticatedProcessingFilter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.authentication.preauth;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.*;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
/**
* Base class for processing filters that handle pre-authenticated authentication
* requests, where it is assumed that the principal has already been authenticated by an
* external system.
* <p>
* The purpose is then only to extract the necessary information on the principal from the
* incoming request, rather than to authenticate them. External authentication systems may
* provide this information via request data such as headers or cookies which the
* pre-authentication system can extract. It is assumed that the external system is
* responsible for the accuracy of the data and preventing the submission of forged
* values.
*
* Subclasses must implement the {@code getPreAuthenticatedPrincipal()} and
* {@code getPreAuthenticatedCredentials()} methods. Subclasses of this filter are
* typically used in combination with a {@code PreAuthenticatedAuthenticationProvider},
* which is used to load additional data for the user. This provider will reject null
* credentials, so the {@link #getPreAuthenticatedCredentials} method should not return
* null for a valid principal.
* <p>
* If the security context already contains an {@code Authentication} object (either from
* a invocation of the filter or because of some other authentication mechanism), the
* filter will do nothing by default. You can force it to check for a change in the
* principal by setting the {@link #setCheckForPrincipalChanges(boolean)
* checkForPrincipalChanges} property.
* <p>
* By default, the filter chain will proceed when an authentication attempt fails in order
* to allow other authentication mechanisms to process the request. To reject the
* credentials immediately, set the
* <tt>continueFilterChainOnUnsuccessfulAuthentication</tt> flag to false. The exception
* raised by the <tt>AuthenticationManager</tt> will the be re-thrown. Note that this will
* not affect cases where the principal returned by {@link #getPreAuthenticatedPrincipal}
* is null, when the chain will still proceed as normal.
*
* @author Luke Taylor
* @author Ruud Senden
* @author Rob Winch
* @since 2.0
*/
public abstract class AbstractPreAuthenticatedProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware {
private ApplicationEventPublisher eventPublisher = null;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
private AuthenticationManager authenticationManager = null;
private boolean continueFilterChainOnUnsuccessfulAuthentication = true;
private boolean checkForPrincipalChanges;
private boolean invalidateSessionOnPrincipalChange = true;
private AuthenticationSuccessHandler authenticationSuccessHandler = null;
private AuthenticationFailureHandler authenticationFailureHandler = null;
/**
* Check whether all required properties have been set.
*/
@Override
public void afterPropertiesSet() {
try {
super.afterPropertiesSet();
}
catch (ServletException e) {
// convert to RuntimeException for passivity on afterPropertiesSet signature
throw new RuntimeException(e);
}
Assert.notNull(authenticationManager, "An AuthenticationManager must be set");
}
/**
* Try to authenticate a pre-authenticated user with Spring Security if the user has
* not yet been authenticated.
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Checking secure context token: "
+ SecurityContextHolder.getContext().getAuthentication());
}
if (requiresAuthentication((HttpServletRequest) request)) {
doAuthenticate((HttpServletRequest) request, (HttpServletResponse) response);
}
chain.doFilter(request, response);
}
/**
* Determines if the current principal has changed. The default implementation tries
*
* <ul>
* <li>If the {@link #getPreAuthenticatedPrincipal(HttpServletRequest)} is a String, the {@link Authentication#getName()} is compared against the pre authenticated principal</li>
* <li>Otherwise, the {@link #getPreAuthenticatedPrincipal(HttpServletRequest)} is compared against the {@link Authentication#getPrincipal()}
* </ul>
*
* <p>
* Subclasses can override this method to determine when a principal has changed.
* </p>
*
* @param request
* @param currentAuthentication
* @return true if the principal has changed, else false
*/
protected boolean principalChanged(HttpServletRequest request, Authentication currentAuthentication) {
Object principal = getPreAuthenticatedPrincipal(request);
if ((principal instanceof String) && currentAuthentication.getName().equals(principal)) {
return false;
}
if (principal != null && principal.equals(currentAuthentication.getPrincipal())) {
return false;
}
if(logger.isDebugEnabled()) {
logger.debug("Pre-authenticated principal has changed to " + principal + " and will be reauthenticated");
}
return true;
}
/**
* Do the actual authentication for a pre-authenticated user.
*/
private void doAuthenticate(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
Authentication authResult;
Object principal = getPreAuthenticatedPrincipal(request);
Object credentials = getPreAuthenticatedCredentials(request);
if (principal == null) {
if (logger.isDebugEnabled()) {
logger.debug("No pre-authenticated principal found in request");
}
return;
}
if (logger.isDebugEnabled()) {
logger.debug("preAuthenticatedPrincipal = " + principal
+ ", trying to authenticate");
}
try {
PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken(
principal, credentials);
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
authResult = authenticationManager.authenticate(authRequest);
successfulAuthentication(request, response, authResult);
}
catch (AuthenticationException failed) {
unsuccessfulAuthentication(request, response, failed);
if (!continueFilterChainOnUnsuccessfulAuthentication) {
throw failed;
}
}
}
private boolean requiresAuthentication(HttpServletRequest request) {
Authentication currentUser = SecurityContextHolder.getContext()
.getAuthentication();
if (currentUser == null) {
return true;
}
if (!checkForPrincipalChanges) {
return false;
}
if(!principalChanged(request, currentUser)) {
return false;
}
logger.debug("Pre-authenticated principal has changed and will be reauthenticated");
if (invalidateSessionOnPrincipalChange) {
SecurityContextHolder.clearContext();
HttpSession session = request.getSession(false);
if (session != null) {
logger.debug("Invalidating existing session");
session.invalidate();
request.getSession();
}
}
return true;
}
/**
* Puts the <code>Authentication</code> instance returned by the authentication
* manager into the secure context.
*/
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, Authentication authResult) throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
if(authenticationSuccessHandler != null) {
authenticationSuccessHandler.onAuthenticationSuccess(request, response, authResult);
}
}
/**
* Ensures the authentication object in the secure context is set to null when
* authentication fails.
* <p>
* Caches the failure exception as a request attribute
*/
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
if (logger.isDebugEnabled()) {
logger.debug("Cleared security context due to exception", failed);
}
request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, failed);
if(authenticationFailureHandler != null) {
authenticationFailureHandler.onAuthenticationFailure(request, response, failed);
}
}
/**
* @param anApplicationEventPublisher The ApplicationEventPublisher to use
*/
public void setApplicationEventPublisher(
ApplicationEventPublisher anApplicationEventPublisher) {
this.eventPublisher = anApplicationEventPublisher;
}
/**
* @param authenticationDetailsSource The AuthenticationDetailsSource to use
*/
public void setAuthenticationDetailsSource(
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource,
"AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
protected AuthenticationDetailsSource<HttpServletRequest, ?> getAuthenticationDetailsSource() {
return authenticationDetailsSource;
}
/**
* @param authenticationManager The AuthenticationManager to use
*/
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/**
* If set to {@code true}, any {@code AuthenticationException} raised by the
* {@code AuthenticationManager} will be swallowed, and the request will be allowed to
* proceed, potentially using alternative authentication mechanisms. If {@code false}
* (the default), authentication failure will result in an immediate exception.
*
* @param shouldContinue set to {@code true} to allow the request to proceed after a
* failed authentication.
*/
public void setContinueFilterChainOnUnsuccessfulAuthentication(boolean shouldContinue) {
continueFilterChainOnUnsuccessfulAuthentication = shouldContinue;
}
/**
* If set, the pre-authenticated principal will be checked on each request and
* compared against the name of the current <tt>Authentication</tt> object. A check to
* determine if {@link Authentication#getPrincipal()} is equal to the principal will
* also be performed. If a change is detected, the user will be reauthenticated.
*
* @param checkForPrincipalChanges
*/
public void setCheckForPrincipalChanges(boolean checkForPrincipalChanges) {
this.checkForPrincipalChanges = checkForPrincipalChanges;
}
/**
* If <tt>checkForPrincipalChanges</tt> is set, and a change of principal is detected,
* determines whether any existing session should be invalidated before proceeding to
* authenticate the new principal.
*
* @param invalidateSessionOnPrincipalChange <tt>false</tt> to retain the existing
* session. Defaults to <tt>true</tt>.
*/
public void setInvalidateSessionOnPrincipalChange(
boolean invalidateSessionOnPrincipalChange) {
this.invalidateSessionOnPrincipalChange = invalidateSessionOnPrincipalChange;
}
/**
* Sets the strategy used to handle a successful authentication.
*/
public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
this.authenticationSuccessHandler = authenticationSuccessHandler;
}
/**
* Sets the strategy used to handle a failed authentication.
*/
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
}
/**
* Override to extract the principal information from the current request
*/
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request);
/**
* Override to extract the credentials (if applicable) from the current request.
* Should not return null for a valid principal, though some implementations may
* return a dummy value.
*/
protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
}