前言
最近重新学习了一下Spring Security,对整个认证流程有了一个自己的认识。但今天写的是一个前置的知识点,Spring Security启动后默认加载的一些过滤器,接着让我们来看看究竟是哪些
1 启动后过滤链总览
[main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain:
org.springframework.security.web.util.matcher.AnyRequestMatcher@1,
[
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@486bc9a4,
org.springframework.security.web.context.SecurityContextPersistenceFilter@8ed9cf,
org.springframework.security.web.header.HeaderWriterFilter@2d2acd89,
org.springframework.security.web.authentication.logout.LogoutFilter@1f193686,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@3b36e000,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@377008df,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@2bc9a775,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@7965a51c,
org.springframework.security.web.session.SessionManagementFilter@1e6dad8,
org.springframework.security.web.access.ExceptionTranslationFilter@3fa76c61,
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@9d1a267
]
2 过滤器分析
2.1 WebAsyncManagerIntegrationFilter
/**
* 将SecurityContext和WebAsyncManager整合起来
* Provides integration between the {@link SecurityContext} and Spring Web's
* {@link WebAsyncManager} by using the
* {@link SecurityContextCallableProcessingInterceptor#beforeConcurrentHandling(org.springframework.web.context.request.NativeWebRequest, Callable)}
* to populate the {@link SecurityContext} on the {@link Callable}.
*
* @author Rob Winch
* @see SecurityContextCallableProcessingInterceptor
*/
public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter {
private static final Object CALLABLE_INTERCEPTOR_KEY = new Object();
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
//从request的attribute中得到或创建WebAsyncManager
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
//从asyncManager中得到SecurityContextCallableProcessingInterceptor,如果为空,下面创建
SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
if (securityProcessingInterceptor == null) {
asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
new SecurityContextCallableProcessingInterceptor());
}
filterChain.doFilter(request, response);
}
}
SecurityContextCallableProcessingInterceptor
/**
* <p>
* Allows for integration with Spring MVC's {@link Callable} support.
* </p>
* <p>
* 当调用拦截器preProcess方法时
* 将注入的SecurityContext存储进SecurityContextHolder
* 当调用拦截器postProcess方法时
* SecurityContextHolder#clearContext()清除SecurityContext
* A {@link CallableProcessingInterceptor} that establishes the injected
* {@link SecurityContext} on the {@link SecurityContextHolder} when
* {@link #preProcess(NativeWebRequest, Callable)} is invoked. It also clear out the
* {@link SecurityContextHolder} by invoking {@link SecurityContextHolder#clearContext()}
* in the {@link #postProcess(NativeWebRequest, Callable, Object)} method.
* </p>
*
* @author Rob Winch
* @since 3.2
*/
public final class SecurityContextCallableProcessingInterceptor extends
CallableProcessingInterceptorAdapter {
private volatile SecurityContext securityContext;
/**
* 当调用beforeConcurrentHandling后,使用SecurityContextHolder的SecurityContext
* Create a new {@link SecurityContextCallableProcessingInterceptor} that uses the
* {@link SecurityContext} from the {@link SecurityContextHolder} at the time
* {@link #beforeConcurrentHandling(NativeWebRequest, Callable)} is invoked.
*/
public SecurityContextCallableProcessingInterceptor() {
}
/**
* Creates a new {@link SecurityContextCallableProcessingInterceptor} with the
* specified {@link SecurityContext}.
* @param securityContext the {@link SecurityContext} to set on the
* {@link SecurityContextHolder} in {@link #preProcess(NativeWebRequest, Callable)}.
* Cannot be null.
* @throws IllegalArgumentException if {@link SecurityContext} is null.
*/
public SecurityContextCallableProcessingInterceptor(SecurityContext securityContext) {
Assert.notNull(securityContext, "securityContext cannot be null");
setSecurityContext(securityContext);
}
@Override
public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) {
if (securityContext == null) {
setSecurityContext(SecurityContextHolder.getContext());
}
}
@Override
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
SecurityContextHolder.setContext(securityContext);
}
@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task,
Object concurrentResult) {
SecurityContextHolder.clearContext();
}
private void setSecurityContext(SecurityContext securityContext) {
this.securityContext = securityContext;
}
}
2.2 SecurityContextPersistenceFilter
/**
* 从配置的SecurityContextRepository中得到信息,填充SecurityContextHolder,当请求完成后又存回
* repository,并清空context holder
* Populates the {@link SecurityContextHolder} with information obtained from the
* configured {@link SecurityContextRepository} prior to the request and stores it back in
* the repository once the request has completed and clearing the context holder. By
* default it uses an {@link HttpSessionSecurityContextRepository}. See this class for
* information <tt>HttpSession</tt> related configuration options.
* <p>
* This filter will only execute once per request, to resolve servlet container
* (specifically Weblogic) incompatibilities.
* <p>
* 此过滤器必须在任何认证处理机制之前,认证处理机制期望在执行的时候SecurityContextHolder中包含一个有效的
* SecurityContext
* This filter MUST be executed BEFORE any authentication processing mechanisms.
* Authentication processing mechanisms (e.g. BASIC, CAS processing filters etc) expect
* the <code>SecurityContextHolder</code> to contain a valid <code>SecurityContext</code>
* by the time they execute.
* <p>
* This is essentially a refactoring of the old
* <tt>HttpSessionContextIntegrationFilter</tt> to delegate the storage issues to a
* separate strategy, allowing for more customization in the way the security context is
* maintained between requests.
* <p>
* forceEagerSessionCreation属性被用作确保session可用,在过滤链执行前
* The <tt>forceEagerSessionCreation</tt> property can be used to ensure that a session is
* always available before the filter chain executes (the default is <code>false</code>,
* as this is resource intensive and not recommended).
*
* @author Luke Taylor
* @since 3.0
*/
public class SecurityContextPersistenceFilter extends GenericFilterBean {
static final String FILTER_APPLIED = "__spring_security_scpf_applied";
private SecurityContextRepository repo;
private boolean forceEagerSessionCreation = false;
public SecurityContextPersistenceFilter() {
//repo具体为HttpSessionSecurityContextRepository
this(new HttpSessionSecurityContextRepository());
}
public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
this.repo = repo;
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) {
// ensure that filter is only applied once per request
chain.doFilter(request, response);
return;
}
final boolean debug = logger.isDebugEnabled();
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (debug && session.isNew()) {
logger.debug("Eagerly created session: " + session.getId());
}
}
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
response);
//从repo中得到SecurityContext(如果没有则使用SecurityContextHolder.createEmptyContext()创建)
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder
.getContext();
// Crucial removal of SecurityContextHolder contents - do this before anything
// else.
//清除SecurityContext,再次保存回repo
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(),
holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
this.forceEagerSessionCreation = forceEagerSessionCreation;
}
}
2.3 HeaderWriterFilter
/**
* 在响应头中写入一些选项,保护浏览器的安全
* Filter implementation to add headers to the current response. Can be useful to add
* certain headers which enable browser protection. Like X-Frame-Options, X-XSS-Protection
* and X-Content-Type-Options.
*
* @author Marten Deinum
* @author Josh Cummings
* @author Ankur Pathak
* @since 3.2
*/
public class HeaderWriterFilter extends OncePerRequestFilter {
/**
* The {@link HeaderWriter} to write headers to the response.
* {@see CompositeHeaderWriter}
*/
private final List<HeaderWriter> headerWriters;
/**
* 标志是否在请求开始时写headers
* Indicates whether to write the headers at the beginning of the request.
*/
private boolean shouldWriteHeadersEagerly = false;
/**
* Creates a new instance.
*
* @param headerWriters the {@link HeaderWriter} instances to write out headers to the
* {@link HttpServletResponse}.
*/
public HeaderWriterFilter(List<HeaderWriter> headerWriters) {
Assert.notEmpty(headerWriters, "headerWriters cannot be null or empty");
this.headerWriters = headerWriters;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (this.shouldWriteHeadersEagerly) {
doHeadersBefore(request, response, filterChain);
} else {
doHeadersAfter(request, response, filterChain);
}
}
private void doHeadersBefore(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
writeHeaders(request, response);
filterChain.doFilter(request, response);
}
private void doHeadersAfter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request,
response);
HeaderWriterRequest headerWriterRequest = new HeaderWriterRequest(request,
headerWriterResponse);
try {
filterChain.doFilter(headerWriterRequest, headerWriterResponse);
} finally {
headerWriterResponse.writeHeaders();
}
}
void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
for (HeaderWriter writer : this.headerWriters) {
writer.writeHeaders(request, response);
}
}
/**
* 允许在请求开始时就写headers
* Allow writing headers at the beginning of the request.
*
* @param shouldWriteHeadersEagerly boolean to allow writing headers at the beginning of the request.
* @author Ankur Pathak
* @since 5.2
*/
public void setShouldWriteHeadersEagerly(boolean shouldWriteHeadersEagerly) {
this.shouldWriteHeadersEagerly = shouldWriteHeadersEagerly;
}
class HeaderWriterResponse extends OnCommittedResponseWrapper {
private final HttpServletRequest request;
HeaderWriterResponse(HttpServletRequest request, HttpServletResponse response) {
super(response);
this.request = request;
}
/*
* (non-Javadoc)
*
* @see org.springframework.security.web.util.OnCommittedResponseWrapper#
* onResponseCommitted()
*/
@Override
protected void onResponseCommitted() {
writeHeaders();
this.disableOnResponseCommitted();
}
protected void writeHeaders() {
if (isDisableOnResponseCommitted()) {
return;
}
HeaderWriterFilter.this.writeHeaders(this.request, getHttpResponse());
}
private HttpServletResponse getHttpResponse() {
return (HttpServletResponse) getResponse();
}
}
static class HeaderWriterRequest extends HttpServletRequestWrapper {
private final HeaderWriterResponse response;
HeaderWriterRequest(HttpServletRequest request, HeaderWriterResponse response) {
super(request);
this.response = response;
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
return new HeaderWriterRequestDispatcher(super.getRequestDispatcher(path), this.response);
}
}
static class HeaderWriterRequestDispatcher implements RequestDispatcher {
private final RequestDispatcher delegate;
private final HeaderWriterResponse response;
HeaderWriterRequestDispatcher(RequestDispatcher delegate, HeaderWriterResponse response) {
this.delegate = delegate;
this.response = response;
}
@Override
public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException {
this.delegate.forward(request, response);
}
@Override
public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException {
this.response.onResponseCommitted();
this.delegate.include(request, response);
}
}
}
2.4 LogoutFilter
/**
* Logs a principal out.
* <p>
* 轮询一系列的LogoutHandler,这些Handler应该按照需要的顺序指定
* Polls a series of {@link LogoutHandler}s. The handlers should be specified in the order
* they are required. Generally you will want to call logout handlers
* <code>TokenBasedRememberMeServices</code> and <code>SecurityContextLogoutHandler</code>
* (in that order).
* <p>
* logout后,重定向将会发生,取决于LogoutSuccessHandler或者logoutSuccessUrl,看谁的构造方法被调用
* After logout, a redirect will be performed to the URL determined by either the
* configured <tt>LogoutSuccessHandler</tt> or the <tt>logoutSuccessUrl</tt>, depending on
* which constructor was used.
*
* @author Ben Alex
* @author Eddú Meléndez
*/
public class LogoutFilter extends GenericFilterBean {
// ~ Instance fields
// ================================================================================================
private RequestMatcher logoutRequestMatcher;
private final LogoutHandler handler;
private final LogoutSuccessHandler logoutSuccessHandler;
// ~ Constructors
// ===================================================================================================
/**
* LogoutSuccessHandler决定登出后的目的地址,LogoutHandler集合去做真正的登出操作
* Constructor which takes a <tt>LogoutSuccessHandler</tt> instance to determine the
* target destination after logging out. The list of <tt>LogoutHandler</tt>s are
* intended to perform the actual logout functionality (such as clearing the security
* context, invalidating the session, etc.).
*/
public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler,
LogoutHandler... handlers) {
//将多个Handler聚合起来
this.handler = new CompositeLogoutHandler(handlers);
Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null");
this.logoutSuccessHandler = logoutSuccessHandler;
setFilterProcessesUrl("/logout");
}
public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) {
this.handler = new CompositeLogoutHandler(handlers);
Assert.isTrue(
!StringUtils.hasLength(logoutSuccessUrl)
|| UrlUtils.isValidRedirectUrl(logoutSuccessUrl),
() -> logoutSuccessUrl + " isn't a valid redirect URL");
SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
if (StringUtils.hasText(logoutSuccessUrl)) {
urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
}
logoutSuccessHandler = urlLogoutSuccessHandler;
setFilterProcessesUrl("/logout");
}
// ~ Methods
// ========================================================================================================
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (requiresLogout(request, response)) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (logger.isDebugEnabled()) {
logger.debug("Logging out user '" + auth
+ "' and transferring to logout destination");
}
this.handler.logout(request, response, auth);
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
/**
* 当需要替换的时候允许子类重写
* Allow subclasses to modify when a logout should take place.
*
* @param request the request
* @param response the response
*
* @return <code>true</code> if logout should occur, <code>false</code> otherwise
*/
protected boolean requiresLogout(HttpServletRequest request,
HttpServletResponse response) {
return logoutRequestMatcher.matches(request);
}
public void setLogoutRequestMatcher(RequestMatcher logoutRequestMatcher) {
Assert.notNull(logoutRequestMatcher, "logoutRequestMatcher cannot be null");
this.logoutRequestMatcher = logoutRequestMatcher;
}
public void setFilterProcessesUrl(String filterProcessesUrl) {
this.logoutRequestMatcher = new AntPathRequestMatcher(filterProcessesUrl);
}
}
2.5 UsernamePasswordAuthenticationFilter
/**
* 处理提交的认证,首先调用AuthenticationProcessingFilter,参数是username和password
* Processes an authentication form submission. Called
* {@code AuthenticationProcessingFilter} prior to Spring Security 3.0.
* <p>
* Login forms must present two parameters to this filter: a username and password. The
* default parameter names to use are contained in the static fields
* {@link #SPRING_SECURITY_FORM_USERNAME_KEY} and
* {@link #SPRING_SECURITY_FORM_PASSWORD_KEY}. The parameter names can also be changed by
* setting the {@code usernameParameter} and {@code passwordParameter} properties.
* <p>
* This filter by default responds to the URL {@code /login}.
*
* @author Ben Alex
* @author Colin Sampaleanu
* @author Luke Taylor
* @since 3.0
*/
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
// ~ Static fields/initializers
// =====================================================================================
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
// ~ Constructors
// ===================================================================================================
//设置父类的RequestMatcher
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
// ~ Methods
// ========================================================================================================
//只能是post方法
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
//构建UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
* Enables subclasses to override the composition of the password, such as by
* including additional values and a separator.
* <p>
* This might be used for example if a postcode/zipcode was required in addition to
* the password. A delimiter such as a pipe (|) should be used to separate the
* password and extended value(s). The <code>AuthenticationDao</code> will need to
* generate the expected password in a corresponding manner.
* </p>
*
* @param request so that request attributes can be retrieved
*
* @return the password that will be presented in the <code>Authentication</code>
* request token to the <code>AuthenticationManager</code>
*/
@Nullable
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}
/**
* Enables subclasses to override the composition of the username, such as by
* including additional values and a separator.
*
* @param request so that request attributes can be retrieved
*
* @return the username that will be presented in the <code>Authentication</code>
* request token to the <code>AuthenticationManager</code>
*/
@Nullable
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(usernameParameter);
}
/**
* 提供这个方法,以便子类可以配置放入身份验证请求的详细信息属性中的内容。
* Provided so that subclasses may configure what is put into the authentication
* request's details property.
*
* @param request that an authentication request is being created for
* @param authRequest the authentication request object that should have its details
* set
*/
protected void setDetails(HttpServletRequest request,
UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
/**
* Sets the parameter name which will be used to obtain the username from the login
* request.
*
* @param usernameParameter the parameter name. Defaults to "username".
*/
public void setUsernameParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.usernameParameter = usernameParameter;
}
/**
* Sets the parameter name which will be used to obtain the password from the login
* request..
*
* @param passwordParameter the parameter name. Defaults to "password".
*/
public void setPasswordParameter(String passwordParameter) {
Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
this.passwordParameter = passwordParameter;
}
/**
* Defines whether only HTTP POST requests will be allowed by this filter. If set to
* true, and an authentication request is received which is not a POST request, an
* exception will be raised immediately and authentication will not be attempted. The
* <tt>unsuccessfulAuthentication()</tt> method will be called as if handling a failed
* authentication.
* <p>
* Defaults to <tt>true</tt> but may be overridden by subclasses.
*/
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getUsernameParameter() {
return usernameParameter;
}
public final String getPasswordParameter() {
return passwordParameter;
}
}
2.6 RequestCacheAwareFilter
/**
* 如果缓存的请求与当前请求匹配,则负责重新构造已保存的请求
* Responsible for reconstituting the saved request if one is cached and it matches the
* current request.
* <p>
* 将会调用配置的RequestCache的getMatchingRequest方法,返回空,则放入原本的request,不为空则放入
* wrappedSavedRequest进入过滤器链
* It will call
* {@link RequestCache#getMatchingRequest(HttpServletRequest, HttpServletResponse)
* getMatchingRequest} on the configured <tt>RequestCache</tt>. If the method returns a
* value (a wrapper of the saved request), it will pass this to the filter chain's
* <tt>doFilter</tt> method. If null is returned by the cache, the original request is
* used and the filter has no effect.
*
* @author Luke Taylor
* @since 3.0
*/
public class RequestCacheAwareFilter extends GenericFilterBean {
private RequestCache requestCache;
public RequestCacheAwareFilter() {
this(new HttpSessionRequestCache());
}
public RequestCacheAwareFilter(RequestCache requestCache) {
Assert.notNull(requestCache, "requestCache cannot be null");
this.requestCache = requestCache;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
(HttpServletRequest) request, (HttpServletResponse) response);
chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
response);
}
}
2.7 SecurityContextHolderAwareRequestFilter
/**
* 这个过滤器使用包装后的request去填充ServletRequest
* A <code>Filter</code> which populates the <code>ServletRequest</code> with a request
* wrapper which implements the servlet API security methods.
* <p>
* {@link SecurityContextHolderAwareRequestWrapper} is extended to provide the following
* additional methods:
* </p>
* <ul>
* 允许用户确定他们是否通过身份验证,如果不则到登录页面
* <li>{@link HttpServletRequest#authenticate(HttpServletResponse)} - Allows the user to
* determine if they are authenticated and if not send the user to the login page. See
* {@link #setAuthenticationEntryPoint(AuthenticationEntryPoint)}.</li>
* 允许用户验证时使用AuthenticationManager
* <li>{@link HttpServletRequest#login(String, String)} - Allows the user to authenticate
* using the {@link AuthenticationManager}. See
* {@link #setAuthenticationManager(AuthenticationManager)}.</li>
* 允许用户使用配置的LogoutHandler登出
* <li>{@link HttpServletRequest#logout()} - Allows the user to logout using the
* {@link LogoutHandler}s configured in Spring Security. See
* {@link #setLogoutHandlers(List)}.</li>
* 复制调用AsyncContext#start方法的线程上的SecurityContextHolder中的SecurityContext
* <li>{@link AsyncContext#start(Runnable)} - Automatically copy the
* {@link SecurityContext} from the {@link SecurityContextHolder} found on the Thread that
* invoked {@link AsyncContext#start(Runnable)} to the Thread that processes the
* {@link Runnable}.</li>
* </ul>
*
*
* @author Orlando Garcia Carmona
* @author Ben Alex
* @author Luke Taylor
* @author Rob Winch
* @author Eddú Meléndez
*/
public class SecurityContextHolderAwareRequestFilter extends GenericFilterBean {
// ~ Instance fields
// ================================================================================================
private String rolePrefix = "ROLE_";
private HttpServletRequestFactory requestFactory;
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationManager authenticationManager;
private List<LogoutHandler> logoutHandlers;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
// ~ Methods
// ========================================================================================================
public void setRolePrefix(String rolePrefix) {
Assert.notNull(rolePrefix, "Role prefix must not be null");
this.rolePrefix = rolePrefix;
updateFactory();
}
/**
* <p>
* 与Servlet 3 APIs集成HttpServletRequest时设置AuthenticationEntryPoint
* Sets the {@link AuthenticationEntryPoint} used when integrating
* {@link HttpServletRequest} with Servlet 3 APIs. Specifically, it will be used when
* {@link HttpServletRequest#authenticate(HttpServletResponse)} is called and the user
* is not authenticated.
* </p>
* <p>
* 如果没有配置,则保留默认的容器行为(调用父类)
* If the value is null (default), then the default container behavior will be be
* retained when invoking {@link HttpServletRequest#authenticate(HttpServletResponse)}
* .
* </p>
* 没有认证时才调用
* @param authenticationEntryPoint the {@link AuthenticationEntryPoint} to use when
* invoking {@link HttpServletRequest#authenticate(HttpServletResponse)} if the user
* is not authenticated.
*/
public void setAuthenticationEntryPoint(
AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
}
/**
* <p>
* Sets the {@link AuthenticationManager} used when integrating
* {@link HttpServletRequest} with Servlet 3 APIs. Specifically, it will be used when
* {@link HttpServletRequest#login(String, String)} is invoked to determine if the
* user is authenticated.
* </p>
* <p>
* If the value is null (default), then the default container behavior will be
* retained when invoking {@link HttpServletRequest#login(String, String)}.
* </p>
*
* @param authenticationManager the {@link AuthenticationManager} to use when invoking
* {@link HttpServletRequest#login(String, String)}
*/
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/**
* <p>
* Sets the {@link LogoutHandler}s used when integrating with
* {@link HttpServletRequest} with Servlet 3 APIs. Specifically it will be used when
* {@link HttpServletRequest#logout()} is invoked in order to log the user out. So
* long as the {@link LogoutHandler}s do not commit the {@link HttpServletResponse}
* (expected), then the user is in charge of handling the response.
* </p>
* <p>
* If the value is null (default), the default container behavior will be retained
* when invoking {@link HttpServletRequest#logout()}.
* </p>
*
* @param logoutHandlers the {@code List<LogoutHandler>}s when invoking
* {@link HttpServletRequest#logout()}.
*/
public void setLogoutHandlers(List<LogoutHandler> logoutHandlers) {
this.logoutHandlers = logoutHandlers;
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(this.requestFactory.create((HttpServletRequest) req,
(HttpServletResponse) res), res);
}
@Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
updateFactory();
}
private void updateFactory() {
String rolePrefix = this.rolePrefix;
this.requestFactory = createServlet3Factory(rolePrefix);
}
/**
* Sets the {@link AuthenticationTrustResolver} to be used. The default is
* {@link AuthenticationTrustResolverImpl}.
*
* @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be
* null.
*/
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
Assert.notNull(trustResolver, "trustResolver cannot be null");
this.trustResolver = trustResolver;
updateFactory();
}
private HttpServletRequestFactory createServlet3Factory(String rolePrefix) {
HttpServlet3RequestFactory factory = new HttpServlet3RequestFactory(rolePrefix);
factory.setTrustResolver(this.trustResolver);
factory.setAuthenticationEntryPoint(this.authenticationEntryPoint);
factory.setAuthenticationManager(this.authenticationManager);
factory.setLogoutHandlers(this.logoutHandlers);
return factory;
}
}
2.8 AnonymousAuthenticationFilter
/**
* 探测SecurityContextHolder中是否存在Authentication,如果需要创建填充一个
* Detects if there is no {@code Authentication} object in the
* {@code SecurityContextHolder}, and populates it with one if needed.
*
* @author Ben Alex
* @author Luke Taylor
*/
public class AnonymousAuthenticationFilter extends GenericFilterBean implements
InitializingBean {
// ~ Instance fields
// ================================================================================================
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
private String key;
private Object principal;
private List<GrantedAuthority> authorities;
/**
* 构造方法创建一个filter,principal属性"anonymousUser",一个权限角色"ROLE_ANONYMOUS"
* Creates a filter with a principal named "anonymousUser" and the single authority
* "ROLE_ANONYMOUS".
*
* @param key the key to identify tokens created by this filter
*/
public AnonymousAuthenticationFilter(String key) {
this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
}
/**
*
* @param key key the key to identify tokens created by this filter
* @param principal the principal which will be used to represent anonymous users
* @param authorities the authority list for anonymous users
*/
public AnonymousAuthenticationFilter(String key, Object principal,
List<GrantedAuthority> authorities) {
Assert.hasLength(key, "key cannot be null or empty");
Assert.notNull(principal, "Anonymous authentication principal must be set");
Assert.notNull(authorities, "Anonymous authorities must be set");
this.key = key;
this.principal = principal;
this.authorities = authorities;
}
// ~ Methods
// ========================================================================================================
@Override
public void afterPropertiesSet() {
Assert.hasLength(key, "key must have length");
Assert.notNull(principal, "Anonymous authentication principal must be set");
Assert.notNull(authorities, "Anonymous authorities must be set");
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
//如果不存在,就创建一个
if (SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication(
createAuthentication((HttpServletRequest) req));
if (logger.isDebugEnabled()) {
logger.debug("Populated SecurityContextHolder with anonymous token: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
chain.doFilter(req, res);
}
protected Authentication createAuthentication(HttpServletRequest request) {
AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
principal, authorities);
auth.setDetails(authenticationDetailsSource.buildDetails(request));
return auth;
}
public void setAuthenticationDetailsSource(
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource,
"AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
public Object getPrincipal() {
return principal;
}
public List<GrantedAuthority> getAuthorities() {
return authorities;
}
}
2.9 SessionManagementFilter
/**
* 检测用户是否从请求开始时就已通过身份验证,如果已经通过,则调用已配置的
* SessionAuthenticationStrategy来执行任何与会话相关的活动,如激活会话固定保护机制或检查多个并发登录
* Detects that a user has been authenticated since the start of the request and, if they
* have, calls the configured {@link SessionAuthenticationStrategy} to perform any
* session-related activity such as activating session-fixation protection mechanisms or
* checking for multiple concurrent logins.
*
* @author Martin Algesten
* @author Luke Taylor
* @since 2.0
*/
public class SessionManagementFilter extends GenericFilterBean {
// ~ Static fields/initializers
// =====================================================================================
static final String FILTER_APPLIED = "__spring_security_session_mgmt_filter_applied";
// ~ Instance fields
// ================================================================================================
private final SecurityContextRepository securityContextRepository;
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private InvalidSessionStrategy invalidSessionStrategy = null;
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
public SessionManagementFilter(SecurityContextRepository securityContextRepository) {
this(securityContextRepository, new SessionFixationProtectionStrategy());
}
public SessionManagementFilter(SecurityContextRepository securityContextRepository,
SessionAuthenticationStrategy sessionStrategy) {
Assert.notNull(securityContextRepository,
"SecurityContextRepository cannot be null");
Assert.notNull(sessionStrategy, "SessionAuthenticationStrategy cannot be null");
this.securityContextRepository = securityContextRepository;
this.sessionAuthenticationStrategy = sessionStrategy;
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (!securityContextRepository.containsContext(request)) {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
//用户已经认证,并且不是匿名用户,调用session strategy
if (authentication != null && !trustResolver.isAnonymous(authentication)) {
// The user has been authenticated during the current request, so call the
// session strategy
try {
sessionAuthenticationStrategy.onAuthentication(authentication,
request, response);
}
catch (SessionAuthenticationException e) {
// The session strategy can reject the authentication
logger.debug(
"SessionAuthenticationStrategy rejected the authentication object",
e);
SecurityContextHolder.clearContext();
failureHandler.onAuthenticationFailure(request, response, e);
return;
}
//立刻把context放入SecurityContextHolder
// Eagerly save the security context to make it available for any possible
// re-entrant
// requests which may occur before the current request completes.
// SEC-1396.
securityContextRepository.saveContext(SecurityContextHolder.getContext(),
request, response);
}
else {
// No security context or authentication present. Check for a session
// timeout
if (request.getRequestedSessionId() != null
&& !request.isRequestedSessionIdValid()) {
if (logger.isDebugEnabled()) {
logger.debug("Requested session ID "
+ request.getRequestedSessionId() + " is invalid.");
}
if (invalidSessionStrategy != null) {
invalidSessionStrategy
.onInvalidSessionDetected(request, response);
return;
}
}
}
}
chain.doFilter(request, response);
}
/**
* 如果没有设置就什么也不做
* Sets the strategy which will be invoked instead of allowing the filter chain to
* proceed, if the user agent requests an invalid session ID. If the property is not
* set, no action will be taken.
*
* @param invalidSessionStrategy the strategy to invoke. Typically a
* {@link SimpleRedirectInvalidSessionStrategy}.
*/
public void setInvalidSessionStrategy(InvalidSessionStrategy invalidSessionStrategy) {
this.invalidSessionStrategy = invalidSessionStrategy;
}
/**
* 验证失败的处理器
* The handler which will be invoked if the <tt>AuthenticatedSessionStrategy</tt>
* raises a <tt>SessionAuthenticationException</tt>, indicating that the user is not
* allowed to be authenticated for this session (typically because they already have
* too many sessions open).
*
*/
public void setAuthenticationFailureHandler(
AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "failureHandler cannot be null");
this.failureHandler = failureHandler;
}
/**
* 默认的是AuthenticationTrustResolverImpl
* Sets the {@link AuthenticationTrustResolver} to be used. The default is
* {@link AuthenticationTrustResolverImpl}.
*
* @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be
* null.
*/
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
Assert.notNull(trustResolver, "trustResolver cannot be null");
this.trustResolver = trustResolver;
}
}
2.10 ExceptionTranslationFilter
/**
* 处理在过滤链中抛出的AccessDeniedException和AuthenticationException
* Handles any <code>AccessDeniedException</code> and <code>AuthenticationException</code>
* thrown within the filter chain.
* <p>
* 这个过滤器是必要的,因为它提供了Java异常和HTTP响应之间的桥梁。它只关心维护用户界面,没有在安全上做增强
* This filter is necessary because it provides the bridge between Java exceptions and
* HTTP responses. It is solely concerned with maintaining the user interface. This filter
* does not do any actual security enforcement.
* <p>
* 如果AuthenticationException被检测到了,会调用authenticationEntryPoint做处理
* If an {@link AuthenticationException} is detected, the filter will launch the
* <code>authenticationEntryPoint</code>. This allows common handling of authentication
* failures originating from any subclass of
* {@link org.springframework.security.access.intercept.AbstractSecurityInterceptor}.
* <p>
* 如果AccessDeniedException被探测到了,会检查是否是匿名用户,如果是,就使用
* authenticationEntryPoint处理,如果不是,则会委派给AccessDeniedHandler处理
* If an {@link AccessDeniedException} is detected, the filter will determine whether or
* not the user is an anonymous user. If they are an anonymous user, the
* <code>authenticationEntryPoint</code> will be launched. If they are not an anonymous
* user, the filter will delegate to the
* {@link org.springframework.security.web.access.AccessDeniedHandler}. By default the
* filter will use {@link org.springframework.security.web.access.AccessDeniedHandlerImpl}.
* <p>
* 使用这个filter,需要配置authenticationEntryPoint和requestCache
* To use this filter, it is necessary to specify the following properties:
* <ul>
* <li><code>authenticationEntryPoint</code> indicates the handler that should commence
* the authentication process if an <code>AuthenticationException</code> is detected. Note
* that this may also switch the current protocol from http to https for an SSL login.</li>
* <li><tt>requestCache</tt> determines the strategy used to save a request during the
* authentication process in order that it may be retrieved and reused once the user has
* authenticated. The default implementation is {@link HttpSessionRequestCache}.</li>
* </ul>
*
* @author Ben Alex
* @author colin sampaleanu
*/
public class ExceptionTranslationFilter extends GenericFilterBean {
// ~ Instance fields
// ================================================================================================
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
private RequestCache requestCache = new HttpSessionRequestCache();
private final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint) {
this(authenticationEntryPoint, new HttpSessionRequestCache());
}
public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint,
RequestCache requestCache) {
Assert.notNull(authenticationEntryPoint,
"authenticationEntryPoint cannot be null");
Assert.notNull(requestCache, "requestCache cannot be null");
this.authenticationEntryPoint = authenticationEntryPoint;
this.requestCache = requestCache;
}
// ~ Methods
// ========================================================================================================
@Override
public void afterPropertiesSet() {
Assert.notNull(authenticationEntryPoint,
"authenticationEntryPoint must be specified");
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
chain.doFilter(request, response);
logger.debug("Chain processed normally");
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
// Try to extract a SpringSecurityException from the stacktrace
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
RuntimeException ase = (AuthenticationException) throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase == null) {
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
AccessDeniedException.class, causeChain);
}
if (ase != null) {
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
}
//处理具体逻辑在这个方法中
handleSpringSecurityException(request, response, chain, ase);
}
else {
// Rethrow ServletExceptions and RuntimeExceptions as-is
if (ex instanceof ServletException) {
throw (ServletException) ex;
}
else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
// Wrap other Exceptions. This shouldn't actually happen
// as we've already covered all the possibilities for doFilter
throw new RuntimeException(ex);
}
}
}
public AuthenticationEntryPoint getAuthenticationEntryPoint() {
return authenticationEntryPoint;
}
protected AuthenticationTrustResolver getAuthenticationTrustResolver() {
return authenticationTrustResolver;
}
private void handleSpringSecurityException(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, RuntimeException exception)
throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
logger.debug(
"Authentication exception occurred; redirecting to authentication entry point",
exception);
sendStartAuthentication(request, response, chain,
(AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
logger.debug(
"Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
exception);
sendStartAuthentication(
request,
response,
chain,
new InsufficientAuthenticationException(
messages.getMessage(
"ExceptionTranslationFilter.insufficientAuthentication",
"Full authentication is required to access this resource")));
}
else {
logger.debug(
"Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
exception);
accessDeniedHandler.handle(request, response,
(AccessDeniedException) exception);
}
}
}
protected void sendStartAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
// SEC-112: Clear the SecurityContextHolder's Authentication, as the
// existing Authentication is no longer considered valid
SecurityContextHolder.getContext().setAuthentication(null);
requestCache.saveRequest(request, response);
logger.debug("Calling Authentication entry point.");
authenticationEntryPoint.commence(request, response, reason);
}
public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
Assert.notNull(accessDeniedHandler, "AccessDeniedHandler required");
this.accessDeniedHandler = accessDeniedHandler;
}
public void setAuthenticationTrustResolver(
AuthenticationTrustResolver authenticationTrustResolver) {
Assert.notNull(authenticationTrustResolver,
"authenticationTrustResolver must not be null");
this.authenticationTrustResolver = authenticationTrustResolver;
}
public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) {
Assert.notNull(throwableAnalyzer, "throwableAnalyzer must not be null");
this.throwableAnalyzer = throwableAnalyzer;
}
/**
* Default implementation of <code>ThrowableAnalyzer</code> which is capable of also
* unwrapping <code>ServletException</code>s.
*/
private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer {
/**
* @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap()
*/
protected void initExtractorMap() {
super.initExtractorMap();
registerExtractor(ServletException.class, throwable -> {
ThrowableAnalyzer.verifyThrowableHierarchy(throwable,
ServletException.class);
return ((ServletException) throwable).getRootCause();
});
}
}
}
2.11 FilterSecurityInterceptor
/**
* 通过过滤器实现对HTTP资源执行安全处理
* Performs security handling of HTTP resources via a filter implementation.
* <p>
* The <code>SecurityMetadataSource</code> required by this security interceptor is of
* type {@link FilterInvocationSecurityMetadataSource}.
* <p>
* Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
* </p>
*
* @author Ben Alex
* @author Rob Winch
*/
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
Filter {
// ~ Static fields/initializers
// =====================================================================================
private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
// ~ Instance fields
// ================================================================================================
private FilterInvocationSecurityMetadataSource securityMetadataSource;
private boolean observeOncePerRequest = true;
// ~ Methods
// ========================================================================================================
/**
* Not used (we rely on IoC container lifecycle services instead)
*
* @param arg0 ignored
*
*/
public void init(FilterConfig arg0) {
}
/**
* Not used (we rely on IoC container lifecycle services instead)
*/
public void destroy() {
}
/**
* Method that is actually called by the filter chain. Simply delegates to the
* {@link #invoke(FilterInvocation)} method.
*
* @param request the servlet request
* @param response the servlet response
* @param chain the filter chain
*
* @throws IOException if the filter chain fails
* @throws ServletException if the filter chain fails
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
this.securityMetadataSource = newSource;
}
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// 第一次调用,进行安全检查
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
/**
* 指示是否将会观察到每个请求一次的处理,默认是true
* Indicates whether once-per-request handling will be observed. By default this is
* <code>true</code>, meaning the <code>FilterSecurityInterceptor</code> will only
* execute once-per-request. Sometimes users may wish it to execute more than once per
* request, such as when JSP forwards are being used and filter security is desired on
* each included fragment of the HTTP request.
*
* @return <code>true</code> (the default) if once-per-request is honoured, otherwise
* <code>false</code> if <code>FilterSecurityInterceptor</code> will enforce
* authorizations for each and every fragment of the HTTP request.
*/
public boolean isObserveOncePerRequest() {
return observeOncePerRequest;
}
public void setObserveOncePerRequest(boolean observeOncePerRequest) {
this.observeOncePerRequest = observeOncePerRequest;
}
}