Overall documentation: Article directory Github: github.com/black-ant
A. The preface
In the last article, we talked about the basis of Secutity. In this article, we will talk about Securiy Filter. Basically, common functions of Security can be found through Filter.
2. A basic Filter case
Let’s take a look at the >>> we registered Filter like this before.
AbstractAuthenticationProcessingFilter filter = new DatabaseAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
Copy the code
Let’s trace how the Filter was loaded:
Step First: Load the Filter into the Security system
// Add to HttpSecurity
C- HttpSecurity
- this.filters. Add (filter) : The filters here are just a List collection/ / trace code you can see this collection will be used to create a DefaultSecurityFilterChain
protected DefaultSecurityFilterChain performBuild(a) throws Exception {
Collections.sort(this.filters, this.comparator);
return new DefaultSecurityFilterChain(this.requestMatcher, this.filters);
}
Copy the code
The HttpSecurity class, which we’ll discuss in more detail later, maintains a Filter set that is loaded into the FilterChain
Step 2: Filter Chain usage mode
// The breakpoint can be traced back to the entire loading process:
/ / Step 1: to build a springSecurityFilterChain
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain(a)throws Exception {
booleanhasConfigurers = webSecurityConfigurers ! =null
&& !webSecurityConfigurers.isEmpty();
if(! hasConfigurers) { WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {});
webSecurity.apply(adapter);
}
return webSecurity.build();
}
// Step 2: websecurity.build () performs the build
public final O build(a) throws Exception {
// You can also see the CAS operation
if (this.building.compareAndSet(false.true)) {
/ / Build
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
/ / Step 3: AbstractConfiguredSecurityBuilder
protected final O doBuild(a) throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
// Mount hooks for subclasses
beforeInit();
init();
buildState = BuildState.CONFIGURING;
Called before each SecurityConfigurer#configure(SecurityBuilder) method is called.
// Subclasses can override this method to mount into the lifecycle when SecurityConfigurer is not required
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
// Actually build the object
O result = performBuild();
buildState = BuildState.BUILT;
returnresult; }}// Step 4: At this point reflection gets the chain of Filters
@Override
protected DefaultSecurityFilterChain performBuild(a) throws Exception {
Collections.sort(filters, comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}
Copy the code
As you can see, the first added to the collection Filter Filter, will eventually be used to build springSecurityFilterChain, what about springSecurityFilterChain?
3. Security Filter Chain
The essence of the Security Filter and WebFilter is the same, only to achieve Seccurity function
>>> Look at the FilterChain call chain:
3.1 FilterChain Creation Procedure
The core of a FilterChain is a VirtualFilterChain. A VirtualFilterChain is generated for each incoming request
VirtualFilterChain is an internal class of FilterChainProxy.
// Added: VirtualFilterChain: Internal filter chain implementation used to pass requests through a list of additional internal filters that match requestsC- VirtualFilterChain ? P-currentposition: the subscript of the currently running Filter p-Firewalledrequest: Can be used to reject potentially dangerous requests and/or wrap them to control their behavior p-list <Filter> additionalFilters: M-dofilter (ServletRequest Request, ServletResponse Response)? - This method will start from2In two dimensions1-currentPosition == SIZE: When executing the last one, reset FirewalledRequest before calling originalChain2- currentPosition ! = size: Execute doFilter in Filter collection successively before thisCopy the code
VirtualFilterChain creation process:
C- FilterChainProxy extends GenericFilterBean ? - GenericFilterBean inherits the Filter interface, This is eventually handled by SpringWeb's Filter by calling m-dofilterinternal-firewalledrequest - creating a VirtualFilterChain and executing the Filter chain// implements Filter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (clearContext) {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}finally{ SecurityContextHolder.clearContext(); request.removeAttribute(FILTER_APPLIED); }}else{ doFilterInternal(request, response, chain); }}private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
// 日志略...
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
return;
}
// An inner class VirtualFilterChain is created
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
// Like a linked list, call in sequence
vfc.doFilter(fwRequest, fwResponse);
}
// Extension: FirewalledRequestC- FirewalledRequest : ? - This is a Request implementation class that works with the HttpFirewall to implement m-reset: Reset method, This method allows FilterChainProxy to reset part or all of the state C-stricthttpfirewall m-getFirewalledrequest - when a request leaves the security filter chain HttpMethod rejectForbiddenHttpMethod (request) : refused to ban - rejectedBlacklistedUrls (request) : refused to blacklist URL -return newFirewalledRequest(Request) : A FirewalledRequest is created, but reset is an empty implementationCopy the code
As you can see, a VirtualFilterChain is created each time doFilterInternal is executed.
Main AbstractAuthenticationProcessingFilter abstract class
C- AbstractAuthenticationProcessingFilter - ! RequiresAuthentication (Request, Response) : Determine whether the request is matched? - note, when we construct DatabaseAuthenticationFilter is introduced into a Matcher verifier - requiresAuthenticationRequestMatcher. Matches (request); - FilterChain continues if no match is performed. - Authentication authResult = attemptAuthentication(request, response);// This method is required to implement class overwrite, in the implementation class we do the following- Add authentication information in Request (account password, more information if needed, cookies, Header, etc.) takes out - builds a Token (DatabaseUserToken) - puts the Token into the Details - calls the ProviderManager through AuthenticationManager To complete the certification// The specific authentication mode will be analyzed in detail later
Copy the code
I used to think that the Security method is to go through all the filters and then execute the Provider. From here, it seems that it adopts the Filter and executes the Provider directly after adaptation
3.2 WebAsyncManagerIntegrationFilter
C- WebAsyncManagerIntegrationFilter ? - Provide integration between SecurityContext and Spring Web's WebBasyncManager? - SecurityContextCallableProcessingInterceptor# beforeConcurrentHandling used to populate SecurityContext C - create a WebAsyncManager? - A central class for managing asynchronous request processing, primarily used as an SPI and not normally used directly by application classes.@Override
protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {
Create a WebAsyncManager
// The central class for managing asynchronous request processing, primarily used as an SPI, is not normally used directly by application classes.
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
/ / if no SecurityContextCallableProcessingInterceptor, create a WebAsyncManager injection
if (securityProcessingInterceptor == null) {
asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,new SecurityContextCallableProcessingInterceptor());
}
filterChain.doFilter(request, response);
}
Copy the code
3.3 SecurityContextPersistenceFilter system
Note that the SecurityContext is the core of the entire authentication, and having a SecurityContext means that the authentication is successful
C- SecurityContextPersistenceFilter ? - This is a mandatory Filter to insert a SecurityContext into the SecurityContextHolder, SecurityContext is at the core of certification containers - SecurityContext contextBeforeChainExecution = repo. LoadContext (holder); ? - note, here will try to get Sesssion Context, is used to verify - SecurityContextHolder. SetContext (contextBeforeChainExecution)? - Set a context-chain-dofilter (holder.getrequest (), holder.getresponse ()); -finallyIn all, after the completion of the filter to insert a contextAfterChainExecution SecurityContextHolder? - note the front is contextBeforeChainExecution// finally
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
/ / remove the Context
SecurityContextHolder.clearContext();
// Save the new Context
repo.saveContext(contextAfterChainExecution, holder.getRequest(),holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
}
Copy the code
3.4 HeaderWriterFilter system
List
Core method: doFilterInternal
- Prepared two HeaderWriterResponse and HeaderWriterRequest, which support secondary wrapping of headers (the original Servlet does not support this).
- After the completion of the filterChain executes headerWriterResponse. WriteHeaders ();
/ / eaderWriterResponse writeHeaders () is to obtain the HeadersConfigurer
C- HeadersConfigurer
M- private List<HeaderWriter> getHeaderWriters(a) {
List<HeaderWriter> writers = new ArrayList<>();
addIfNotNull(writers, contentTypeOptions.writer);
addIfNotNull(writers, xssProtection.writer);
addIfNotNull(writers, cacheControl.writer);
addIfNotNull(writers, hsts.writer);
addIfNotNull(writers, frameOptions.writer);
addIfNotNull(writers, hpkp.writer);
addIfNotNull(writers, contentSecurityPolicy.writer);
addIfNotNull(writers, referrerPolicy.writer);
addIfNotNull(writers, featurePolicy.writer);
writers.addAll(headerWriters);
return writers;
}
Copy the code
3.5 LogoutFilter
The LogoutFilter allows you to customize the LogoutHandler, as you can see in the constructor. By default, the logout address is used to block the request
public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler... handlers) {
this.handler = new CompositeLogoutHandler(handlers);
Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null");
this.logoutSuccessHandler = logoutSuccessHandler;
setFilterProcessesUrl("/logout");
}
// LogoutFilter doFilter logicM- doFilter ? - All you need to do is call handler to execute the logout logic and call the LogoutSuccess logicpublic 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();
// Execute the LogoutHandler implementation class
this.handler.logout(request, response, auth);
Execute the LogoutSuccessHandler implementation class
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
// PS: This Filter has some limitations, can not handle multiple handlers, can consider a custom Filter
// The Handler implementation class will be covered later
Copy the code
3.6 CsrfFilter
As you learned earlier, in order for the synchronizer token pattern to protect against CSRF attacks, the actual CSRF token must be included in the HTTP request. This must be included in the part of the request that the browser does not automatically include in the HTTP request (that is, form parameters, HTTP, and so on).
Spring Security’s CsrfFilter treats a CsrfToken as an HttpServletRequest public property named _cSRf
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
/ / load CsrfToken
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
final boolean missingToken = csrfToken == null;
if (missingToken) {
// Create a new one if it is missing
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
// Compare the actual tokens across domains
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if(! csrfToken.getToken().equals(actualToken)) {if (missingToken) {
this.accessDeniedHandler.handle(request, response,new MissingCsrfTokenException(actualToken));
} else {
this.accessDeniedHandler.handle(request, response,new InvalidCsrfTokenException(csrfToken, actualToken));
}
return;
}
filterChain.doFilter(request, response);
}
Copy the code
3.9 Other Scattered Tokens
If the cached request matches the current request, it is responsible for restructuring the saved request
The whole core code is mainly two sentences: which is mainly encapsulated a new wrappedSavedRequest
HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
(HttpServletRequest) request, (HttpServletResponse) response);
chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,response);
Copy the code
SecurityContextHolderAwareRequestFilter
A Filter that fills the ServletRequest with a Request wrapper that implements the servlet API security methods. In simple terms, it is a Filter that encapsulates the Request. The encapsulated HttpServletRequest provides many additional functions
- It. The authenticate () – allows the user to determine whether they are validated, if not, then send the user to the login page
- Httpservletrequest.login () – allows the user to authenticate using AuthenticationManager
- It. Logout () – allows the user to use Spring Security configured in LogoutHandlers cancellation
SessionManagementFilter
SessionManagementFilter provides multiple objects for Session activity after the user has been authenticated,** to activate Session fixation protection or to check for multiple concurrent logins **
- SecurityContextRepository securityContextRepository;
- SessionAuthenticationStrategy sessionAuthenticationStrategy;
3.8 ExceptionTranslationFilter
Action: Handles any AccessDeniedException and AuthenticationException thrown in the filter chain.
If AuthenticationException is detected, the filter launches authenticationEntryPoint. This allows gm to handle any subclass from the AbstractSecurityInterceptor authentication failed.
sendStartAuthentication(request, response, chain,(AuthenticationException) exception);
If an AccessDeniedException is detected, the filter determines whether the user is anonymous.
- If they are anonymous users, authenticationEntryPoint is started.
- If they are not anonymous users, the filter is delegated to the AccessDeniedHandler.
- By default, the filter will use AccessDeniedHandlerImpl.
sendStartAuthentication(request,response,chain,new InsufficientAuthenticationException(
messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication"."Full authentication is required to access this resource")));
Copy the code
(PS: because it is a chain structure, it is the last one and also the outermost one.)
The core is to handle it through a catch
try {
chain.doFilter(request, response);
}catch (Exception ex) {
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
RuntimeException ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
// Get the sequence type
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);
}
// Handle SpringSecurityException exclusively
handleSpringSecurityException(request, response, chain, ase);
}else {
if (ex instanceof ServletException) {
throw (ServletException) ex;
}else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
throw newRuntimeException(ex); }}Copy the code
- First of all, ExceptionTranslationFilter call FilterChain. DoFilter (request, response) to the rest of the calling application.
- If the user is not authenticated, or if it is AuthenticationException, then authentication is started.
- Clear Security contextholder
- Save HttpServletRequest in RequestCache. RequestCache is used to get the original request when the user successfully authenticates
- AuthenticationEntryPoint is used to request credentials from the client.
- For example, it might redirect to a login page or send a WWW-Authenticate header.
- Otherwise, if it is an AccessDeniedException, then AccessDenied
If the application is not thrown AccessDeniedException or AuthenticationException, then ExceptionTranslationFilter can’t do anything.
4. Business flow
Talking about Filter, we must talk about the corresponding business of Filter in detail. Some simple business of Filter is mentioned above. In this section, we will talk about the relatively large business process:
4.1 Service matching for HttpSecurity
When configuring Security, we usually configure parameters such as Request Match, for example:
http.authorizeRequests()
.antMatchers("/test/**").permitAll()
.antMatchers("/before/**").permitAll()
.antMatchers("/index").permitAll()
.antMatchers("/").permitAll()
.anyRequest().authenticated() // All other requests require validation to access
.and()
.formLogin()
.loginPage("/login") // Define the login page "/login" to allow access
.defaultSuccessUrl("/home") // After successful login, the default jump to "list"
.successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenctiationFailureHandler).permitAll().and()
.logout() // The default "/logout" allows access
.logoutSuccessUrl("/index")
.permitAll();
Copy the code
So how do these parameters work?
Step End: indicates the final matching object
Let’s work backwards from the overall business process, whose final object is a RequestMatcher implementation class
Note that our antMatchers type generates a variety of different implementation classes:
- AndRequestMatcher: Request path
- IpAddressMatcher: indicates the IP address
- MediaTypeRequestMatcher: Media type
. And so on. I won’t go into the details
Once you have the implementation class, call matches to return the final result
public boolean matches(HttpServletRequest request) {
return requestMatcher.matches(request);
}
Copy the code
Step Start: Look at the starting point of the request
If we find the final match, we can make a breakpoint, and the whole call chain is clear
// Step1 : FilterChainProxy
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(fwRequest);
/ /... omit
// Execute the Filter chain
if (filters == null || filters.size() == 0) {
chain.doFilter(fwRequest, fwResponse);
}
// Step2: getFilters Filter Chain
for (SecurityFilterChain chain : filterChains) {
// If the address matches, the object Filter chain is executed
if (chain.matches(request)) {
returnchain.getFilters(); }}Copy the code
As can be seen here, if the interception is not successful, it should be run directly, so the starting point of all Security is Filter
Added five.
5.1 DelegatingFilterProxy supplement
Security integrates Security into WebFilter’s system through DelegatingFilterProxy. The main process is as follows:
- C- DelegatingFilterProxy # doFilter
- C- DelegatingFilterProxy # invokeDelegate
- C- FilterChainProxy # doFilter
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
PIC51001: delegate object structure
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("...");
}
delegateToUse = initDelegate(wac);
}
this.delegate = delegateToUse; }}// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}
protected void invokeDelegate( Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// FilterChainProxy is invoked to enter the Security system
delegate.doFilter(request, response, filterChain);
}
Copy the code
PIC51001: Delegate object structure
Continue to add: delegate initialization, get FilterChain
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = getTargetBeanName();
/ / for springSecurityFilterChain TargetBeanName here
Filter delegate = wac.getBean(targetBeanName, Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
Copy the code
conclusion
The starting point of Spring Security is Filter. Traces of commonly used functions can be found through Filter. Later, we will continue to analyze things at the lower level to see what has happened below
The essence of a SecurityFilter is the same as that of a WebFilter. Security centralizes the SecurityFilterChain into the Filter system via a DelegatingFilterProxy
The core of FilterChain is a VirtualFilterChain. Each request will generate a VirtualFilterChain, and all Filter classes will be added
The Filter includes:
- SecurityContextPersistenceFilter: persistence operation on SecurityContext
- HeaderWriterFilter: Secondary processing of the Header, because a lot of authentication information is put in the Header, is also an extremely important class
- LogoutFilter: Intercepts the logout request and exits
- ExceptionTranslationFilter: the process of abnormal process (AccessDeniedException and AuthenticationException)
Filter intercepts matches to determine whether the Filter logic needs to be executed
Update log
V20210803: Add DelegatingFilterProxy logic