Spring Security 的过滤器都不是直接内嵌到 Servlet Filter 中的,而是通过 FilterChainProxy 来统一管理,即所有 Filter 的执行都是在 FilterChainProxy 中进行管理的
// FilterChainProxy.VirtualFilterChain
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.currentPosition == this.size) {
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> "Secured " + requestLine(this.firewalledRequest)));
}
// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();
// 执行 web 中的过滤器
this.originalChain.doFilter(request, response);
return;
}
// 执行 Spring Security过滤器链中的过滤器
this.currentPosition++;
//additionalFilters中定义了SpringSecurity过滤器链中的所有过滤器
Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(),
this.currentPosition, this.size));
}
nextFilter.doFilter(request, response, this);
}
}
默认过滤器
ExceptionTranslationFilter
AuthorizationFilter
当我们使用自定义认证成功后,会将用户信息转换成 `Authentication` 对象,并将该对象保存到线程上下文中
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(token);
filterChain.doFilter(request, response);
AuthorizationFilter 会获取 Authentication 信息来校验当前用户是否通过该次访问
// LoginUrlAuthenticationEntryPoint.java
/**
* Performs the redirect (or forward) to the login form URL.
*/
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
if (!this.useForward) {
// redirect to login page. Use https if forceHttps true
String redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
return;
}
String redirectUrl = null;
if (this.forceHttps && "http".equals(request.getScheme())) {
// First redirect the current request to HTTPS. When that request is received,
// the forward to the login page will be used.
redirectUrl = buildHttpsRedirectUrlForRequest(request);
}
if (redirectUrl != null) {
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
return;
}
String loginForm = determineUrlToUseForThisRequest(request, response, authException);
logger.debug(LogMessage.format("Server side forward to: %s", loginForm));
RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
dispatcher.forward(request, response);
return;
}
自定义未认证处理器
public class UnAuthenticationHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().println("必须认证之后才能访问!");
}
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
.mvcMatchers("/index")
.permitAll()
.anyRequest().authenticated()
.and().formLogin()
.successHandler(new LoginSuccessHandler())
.failureHandler(new LoginFailureHandler())
// 添加自定义处理器
.and().exceptionHandling().authenticationEntryPoint(new UnAuthenticationHandler())
.accessDeniedHandler(new UnAbleAccessHandler())
.and().logout().logoutSuccessHandler(new LogoutHandler())
.and().userDetailsService(userDetailsService()); // 注销登入处理器
return http.csrf().disable().build();
}
}