Spring Security 6.x 系列(11)—— Form表单认证流程

一、前言

在本系列文章中介绍了过滤器和相关认证组件,对部分源码也进行详细分析。

本章主要学习 Spring Security 中通过 HTML 表单提供用户名密码的认证流程。

二、配置表单登录

默认情况下,Spring Security 表单登录处于启用状态。 但是,一旦提供了任何基于 servlet 的配置,就必须显式提供基于表单的登录。

一个最小的显式 Java 配置示例:

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {/*** 构建SecurityFilterChain** @param http* @return* @throws Exception*/@BeanSecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {// 配置所有http请求必须经过认证http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated());// 开启表单认证(默认配置)http.formLogin(Customizer.withDefaults());// 构造器构建SecurityFilterChain对象return http.build();}/*** 配置登录名密码** @return*/@Beanpublic UserDetailsService userDetailsService() {UserDetails user = User.withUsername("admin").password("{noop}123456").roles("USER").build();return new InMemoryUserDetailsManager(new UserDetails[]{user});}
}

配置中 http.formLogin(Customizer.withDefaults()) 表示开启表单认证,该方法中( SecurityFilterChain 的构造器 HttpSecurity )应用了一个表单登录配置器 FormLoginConfigurer

public HttpSecurity formLogin(Customizer<FormLoginConfigurer<HttpSecurity>> formLoginCustomizer) throws Exception {formLoginCustomizer.customize(getOrApply(new FormLoginConfigurer<>()));return HttpSecurity.this;
}

在本系列章节中对 FormLoginConfigurer 进行过介绍,会向 SecurityFilterChain 添加 UsernamePasswordAuthenticationFilter 用于表单认证,并设置用户名和密码对应的请求参数名称:

public FormLoginConfigurer() {super(new UsernamePasswordAuthenticationFilter(), null);usernameParameter("username");passwordParameter("password");
}

更多 FormLoginConfigurer 介绍请见:Spring Security 6.x 系列(10)—— SecurityConfigurer 配置器及其分支实现源码分析(二)

添加一个访问测试接口:

@GetMapping("/private")
public String hello() {return "hello spring security";
}

三、认证流程分析

3.1 重定向登录页

上文配置所有 http 请求必须经过认证,在未登录时访问 /private 接口,会重定向登录页,流程如下:

在这里插入图片描述

① 首先,用户向未授权的资源 /private 发出未经身份认证的请求。

Spring SecurityAuthorizationFilter 抛出 AccessDeniedException 异常。

③ 由于用户未经过身份验证,因此 ExceptionTranslationFilter 将启动“启动身份验证”,并使用配置的 AuthenticationEntryPoint 将重定向发送到登录页。 在大多数情况下,是 LoginUrlAuthenticationEntryPoint 的实例。

④ 浏览器请求重定向到的登录页面。

⑤ 呈现默认登录页面。

下面我们会针对上述流程进行详细的源码分析。

3.1.1 抛出 AccessDeniedException 异常

/private 发出未经身份认证的请求会依次通过下述所有过滤器:

Security filter chain: [DisableEncodeUrlFilterWebAsyncManagerIntegrationFilterSecurityContextHolderFilterHeaderWriterFilterCsrfFilterLogoutFilterUsernamePasswordAuthenticationFilterDefaultLoginPageGeneratingFilterDefaultLogoutPageGeneratingFilterRequestCacheAwareFilterSecurityContextHolderAwareRequestFilterAnonymousAuthenticationFilterExceptionTranslationFilterAuthorizationFilter
]

在进入最后一个过滤器 AuthorizationFilter 时,会对当前请求做最后的权限效验,如何未被授权,会抛出 AccessDeniedException 异常:

在这里插入图片描述
首先 AuthorizationFilter 会取出当前用户认证信息,因当前请求未经身份认证,获取的 AuthenticationAnonymousAuthenticationFilter 创建的匿名用户

在这里插入图片描述
接着使用 authorizationManager 授权管理器对当前认证信息进行检查,因为是匿名用户,所有判定当前请求无权访问,抛出 AccessDeniedException 异常:

在这里插入图片描述

3.1.2 异常处理

抛出 AccessDeniedException 异常会被 ExceptionTranslationFilter 捕获:

在这里插入图片描述

ExceptionTranslationFilter 根据异常类型进行相应处理:

private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,FilterChain chain, RuntimeException exception) throws IOException, ServletException {if (exception instanceof AuthenticationException) {handleAuthenticationException(request, response, chain, (AuthenticationException) exception);}else if (exception instanceof AccessDeniedException) {handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception);}
}

分别为:

  • 调用 handleAuthenticationException 方法处理 AuthenticationException 异常类型
  • 调用 handleAccessDeniedException 方法处理 AccessDeniedException 异常类型

因本次异常类型为 AccessDeniedException,故调用 handleAccessDeniedException 方法:

private void handleAuthenticationException(HttpServletRequest request, HttpServletResponse response,FilterChain chain, AuthenticationException exception) throws ServletException, IOException {this.logger.trace("Sending to authentication entry point since authentication failed", exception);sendStartAuthentication(request, response, chain, exception);
}private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response,FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();boolean isAnonymous = this.authenticationTrustResolver.isAnonymous(authentication);if (isAnonymous || this.authenticationTrustResolver.isRememberMe(authentication)) {if (logger.isTraceEnabled()) {logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied",authentication), exception);}sendStartAuthentication(request, response, chain,new InsufficientAuthenticationException(this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication","Full authentication is required to access this resource")));}else {if (logger.isTraceEnabled()) {logger.trace(LogMessage.format("Sending %s to access denied handler since access is denied", authentication),exception);}this.accessDeniedHandler.handle(request, response, exception);}
}

因为是匿名用户需接着调用 sendStartAuthentication 方法缓存请求(用于认证成功后再重定向回此请求),并调用AuthenticationEntryPoint 生成认证入口:

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 validSecurityContext context = this.securityContextHolderStrategy.createEmptyContext();this.securityContextHolderStrategy.setContext(context);this.requestCache.saveRequest(request, response);this.authenticationEntryPoint.commence(request, response, reason);
}

3.1.3 重定向

AuthenticationEntryPoint 将重定向发送到登录页。 在大多数情况下,是 LoginUrlAuthenticationEntryPoint 的实例:

在这里插入图片描述

调用 commence 方法进行重定向转发

在这里插入图片描述

上述流程运行日志:

2023-12-18T14:55:16.292+08:00  INFO 24072 --- [nio-9000-exec-3] Spring Security Debugger                 : ************************************************************Request received for GET '/priavte':org.apache.catalina.connector.RequestFacade@1299acf6servletPath:/priavte
pathInfo:null
headers: 
host: 127.0.0.1:9000
connection: keep-alive
sec-ch-ua: "Google Chrome";v="117", "Not;A=Brand";v="8", "Chromium";v="117"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
sec-fetch-site: none
sec-fetch-mode: navigate
sec-fetch-user: ?1
sec-fetch-dest: document
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cookie: mttsmart-access-token=1ba0d263-c764-4916-8dfd-79fad8a9198f; mttsmart-refresh-token=dqcIyoWLvZzYbNvAGGRKykCkF1oB6k0YvrRsg-bT4XFoI-5dEKmbdYTST5op_s8UxzKaR7ON6N8H_N4kqAHtOtd9GdRtEznZqQ0F96bxIbvpOkxM-ReyVaDgRqTdCsHi; token=034a4acc-fd5f-46b3-804f-dab458b9157e; refresh_token=C-NYlk79Hkxc1oW8iZ9aFF-okp0ZDmODCwIQtcGcBFS3FLWViNLPW50ccVQzquYo1rYqK1TUqmMvk9ZnqsLIOxjI4Mff-UxuMwJKExN0uuXP_wRxrrGytXqvSuqkIvDI; JSESSIONID=6A5FA61657FA3C84115F42CBF6CE370BSecurity filter chain: [DisableEncodeUrlFilterWebAsyncManagerIntegrationFilterSecurityContextHolderFilterHeaderWriterFilterCsrfFilterLogoutFilterUsernamePasswordAuthenticationFilterDefaultLoginPageGeneratingFilterDefaultLogoutPageGeneratingFilterRequestCacheAwareFilterSecurityContextHolderAwareRequestFilterAnonymousAuthenticationFilterExceptionTranslationFilterAuthorizationFilter
]************************************************************2023-12-18T14:55:16.292+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@40729f01, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@d535a3d, org.springframework.security.web.context.SecurityContextHolderFilter@7c2924d7, org.springframework.security.web.header.HeaderWriterFilter@9efcd90, org.springframework.security.web.csrf.CsrfFilter@3a1706e1, org.springframework.security.web.authentication.logout.LogoutFilter@4ecd00b5, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@506aabf6, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@3eb3232b, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@2d760326, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@6587305a, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@74d6736, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@9e54c59, org.springframework.security.web.access.ExceptionTranslationFilter@365cdacf, org.springframework.security.web.access.intercept.AuthorizationFilter@3330f3ad]] (1/1)
2023-12-18T14:55:16.292+08:00 DEBUG 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Securing GET /priavte
2023-12-18T14:55:16.292+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Invoking DisableEncodeUrlFilter (1/14)
2023-12-18T14:55:16.292+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Invoking WebAsyncManagerIntegrationFilter (2/14)
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderFilter (3/14)
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Invoking HeaderWriterFilter (4/14)
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Invoking CsrfFilter (5/14)
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.csrf.CsrfFilter         : Did not protect against CSRF since request did not match CsrfNotRequired [TRACE, HEAD, GET, OPTIONS]
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (6/14)
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Ant [pattern='/logout', POST]
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Invoking UsernamePasswordAuthenticationFilter (7/14)
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] w.a.UsernamePasswordAuthenticationFilter : Did not match request to Ant [pattern='/login', POST]
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Invoking DefaultLoginPageGeneratingFilter (8/14)
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Invoking DefaultLogoutPageGeneratingFilter (9/14)
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] .w.a.u.DefaultLogoutPageGeneratingFilter : Did not render default logout page since request did not match [Ant [pattern='/logout', GET]]
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Invoking RequestCacheAwareFilter (10/14)
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.s.w.s.HttpSessionRequestCache        : matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderAwareRequestFilter (11/14)
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Invoking AnonymousAuthenticationFilter (12/14)
2023-12-18T14:55:16.293+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Invoking ExceptionTranslationFilter (13/14)
2023-12-18T14:55:16.294+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy        : Invoking AuthorizationFilter (14/14)
2023-12-18T14:55:16.294+08:00 TRACE 24072 --- [nio-9000-exec-3] estMatcherDelegatingAuthorizationManager : Authorizing SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@1c5e50d5]
2023-12-18T14:55:16.294+08:00 TRACE 24072 --- [nio-9000-exec-3] estMatcherDelegatingAuthorizationManager : Checking authorization on SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@1c5e50d5] using org.springframework.security.authorization.AuthenticatedAuthorizationManager@3d9544f9
2023-12-18T14:55:23.778+08:00 TRACE 24072 --- [nio-9000-exec-3] w.c.HttpSessionSecurityContextRepository : Did not find SecurityContext in HttpSession 6A5FA61657FA3C84115F42CBF6CE370B using the SPRING_SECURITY_CONTEXT session attribute
2023-12-18T14:55:23.778+08:00 TRACE 24072 --- [nio-9000-exec-3] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]
2023-12-18T14:55:23.778+08:00 TRACE 24072 --- [nio-9000-exec-3] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]
2023-12-18T14:55:23.778+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=6A5FA61657FA3C84115F42CBF6CE370B], Granted Authorities=[ROLE_ANONYMOUS]]
2023-12-18T15:18:13.558+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.s.w.a.ExceptionTranslationFilter     : Sending AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=6A5FA61657FA3C84115F42CBF6CE370B], Granted Authorities=[ROLE_ANONYMOUS]] to authentication entry point since access is deniedorg.springframework.security.access.AccessDeniedException: Access Deniedat org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:98) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter.doFilterInternal(DefaultLogoutPageGeneratingFilter.java:58) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.12.jar:6.0.12]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter.doFilter(DefaultLoginPageGeneratingFilter.java:188) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter.doFilter(DefaultLoginPageGeneratingFilter.java:174) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:227) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:221) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:117) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.12.jar:6.0.12]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.12.jar:6.0.12]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.12.jar:6.0.12]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.12.jar:6.0.12]at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.debug.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:90) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:78) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:67) ~[spring-security-web-6.1.4.jar:6.1.4]at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352) ~[spring-web-6.0.12.jar:6.0.12]at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268) ~[spring-web-6.0.12.jar:6.0.12]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.12.jar:6.0.12]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.12.jar:6.0.12]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.0.12.jar:6.0.12]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.12.jar:6.0.12]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.0.12.jar:6.0.12]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.12.jar:6.0.12]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.13.jar:10.1.13]at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]2023-12-18T15:18:13.561+08:00 DEBUG 24072 --- [nio-9000-exec-3] o.s.s.w.s.HttpSessionRequestCache        : Saved request http://127.0.0.1:9000/priavte?continue to session
2023-12-18T15:18:13.561+08:00 DEBUG 24072 --- [nio-9000-exec-3] o.s.s.web.DefaultRedirectStrategy        : Redirecting to http://127.0.0.1:9000/login
2023-12-18T15:18:13.562+08:00 TRACE 24072 --- [nio-9000-exec-3] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match request to [Is Secure]

3.1.4 生成默认页面

重定向后浏览器地址变为:http://127.0.0.1:9000/login,发起 Get 请求,此时又开始执行过滤器:

2023-12-18T15:18:13.566+08:00  INFO 24072 --- [nio-9000-exec-5] Spring Security Debugger                 : ************************************************************Request received for GET '/login':org.apache.catalina.connector.RequestFacade@13754d1bservletPath:/login
pathInfo:null
headers: 
host: 127.0.0.1:9000
connection: keep-alive
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
sec-fetch-site: none
sec-fetch-mode: navigate
sec-fetch-user: ?1
sec-fetch-dest: document
sec-ch-ua: "Google Chrome";v="117", "Not;A=Brand";v="8", "Chromium";v="117"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cookie: mttsmart-access-token=1ba0d263-c764-4916-8dfd-79fad8a9198f; mttsmart-refresh-token=dqcIyoWLvZzYbNvAGGRKykCkF1oB6k0YvrRsg-bT4XFoI-5dEKmbdYTST5op_s8UxzKaR7ON6N8H_N4kqAHtOtd9GdRtEznZqQ0F96bxIbvpOkxM-ReyVaDgRqTdCsHi; token=034a4acc-fd5f-46b3-804f-dab458b9157e; refresh_token=C-NYlk79Hkxc1oW8iZ9aFF-okp0ZDmODCwIQtcGcBFS3FLWViNLPW50ccVQzquYo1rYqK1TUqmMvk9ZnqsLIOxjI4Mff-UxuMwJKExN0uuXP_wRxrrGytXqvSuqkIvDI; JSESSIONID=6A5FA61657FA3C84115F42CBF6CE370BSecurity filter chain: [DisableEncodeUrlFilterWebAsyncManagerIntegrationFilterSecurityContextHolderFilterHeaderWriterFilterCsrfFilterLogoutFilterUsernamePasswordAuthenticationFilterDefaultLoginPageGeneratingFilterDefaultLogoutPageGeneratingFilterRequestCacheAwareFilterSecurityContextHolderAwareRequestFilterAnonymousAuthenticationFilterExceptionTranslationFilterAuthorizationFilter
]************************************************************2023-12-18T15:18:13.566+08:00 TRACE 24072 --- [nio-9000-exec-5] o.s.security.web.FilterChainProxy        : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@40729f01, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@d535a3d, org.springframework.security.web.context.SecurityContextHolderFilter@7c2924d7, org.springframework.security.web.header.HeaderWriterFilter@9efcd90, org.springframework.security.web.csrf.CsrfFilter@3a1706e1, org.springframework.security.web.authentication.logout.LogoutFilter@4ecd00b5, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@506aabf6, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@3eb3232b, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@2d760326, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@6587305a, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@74d6736, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@9e54c59, org.springframework.security.web.access.ExceptionTranslationFilter@365cdacf, org.springframework.security.web.access.intercept.AuthorizationFilter@3330f3ad]] (1/1)
2023-12-18T15:18:13.567+08:00 DEBUG 24072 --- [nio-9000-exec-5] o.s.security.web.FilterChainProxy        : Securing GET /login
2023-12-18T15:18:13.567+08:00 TRACE 24072 --- [nio-9000-exec-5] o.s.security.web.FilterChainProxy        : Invoking DisableEncodeUrlFilter (1/14)
2023-12-18T15:18:13.567+08:00 TRACE 24072 --- [nio-9000-exec-5] o.s.security.web.FilterChainProxy        : Invoking WebAsyncManagerIntegrationFilter (2/14)
2023-12-18T15:18:13.567+08:00 TRACE 24072 --- [nio-9000-exec-5] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderFilter (3/14)
2023-12-18T15:18:13.567+08:00 TRACE 24072 --- [nio-9000-exec-5] o.s.security.web.FilterChainProxy        : Invoking HeaderWriterFilter (4/14)
2023-12-18T15:18:13.568+08:00 TRACE 24072 --- [nio-9000-exec-5] o.s.security.web.FilterChainProxy        : Invoking CsrfFilter (5/14)
2023-12-18T15:18:13.568+08:00 TRACE 24072 --- [nio-9000-exec-5] o.s.security.web.csrf.CsrfFilter         : Did not protect against CSRF since request did not match CsrfNotRequired [TRACE, HEAD, GET, OPTIONS]
2023-12-18T15:18:13.568+08:00 TRACE 24072 --- [nio-9000-exec-5] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (6/14)
2023-12-18T15:18:13.568+08:00 TRACE 24072 --- [nio-9000-exec-5] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Ant [pattern='/logout', POST]
2023-12-18T15:18:13.568+08:00 TRACE 24072 --- [nio-9000-exec-5] o.s.security.web.FilterChainProxy        : Invoking UsernamePasswordAuthenticationFilter (7/14)
2023-12-18T15:18:13.568+08:00 TRACE 24072 --- [nio-9000-exec-5] w.a.UsernamePasswordAuthenticationFilter : Did not match request to Ant [pattern='/login', POST]
2023-12-18T15:18:13.568+08:00 TRACE 24072 --- [nio-9000-exec-5] o.s.security.web.FilterChainProxy        : Invoking DefaultLoginPageGeneratingFilter (8/14)
2023-12-18T15:18:13.568+08:00 TRACE 24072 --- [nio-9000-exec-5] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match request to [Is Secure]

在经过 DefaultLoginPageGeneratingFilter 时,进行默认登录页处理后续过滤器操作不再执行

在这里插入图片描述

调用 response 直接输出一个页面,并 return 不再执行后续操作,最后登录页面效果:

在这里插入图片描述

3.2 表单登录

提交用户名和密码后,将对用户名和密码进行身份验:

在这里插入图片描述
①当用户输入用户名和密码提交登录,登录请求会被 UsernamePasswordAuthenticationFilter 处理,预构建认证对象 UsernamePasswordAuthenticationToken(未认证) 。

②接下来调用 AuthenticationManager 进行认证。

ProviderManagerAuthenticationManager 的实现类,ProviderManager 遍历所有的认证提供者,DaoAuthenticationProvider 符合 Form 表单认证。

③如果身份认证失败:

  • 清理 SecurityContextHolder

  • RememberMeServices#loginFail被调用。 如果未配置“记住我”,则为空操作。

  • AuthenticationFailureHandler 被调用,重定向 /login?error 登录页面,显示错误信息 。

④如果身份验证成功:

  • SessionAuthenticationStrategy 会话处理。

  • SecurityContextHolder 上存储认证信息。

  • RememberMeServices#loginSuccess 被调用。 如果未配置“记住我”,则为空操作。

  • ApplicationEventPublisher#InteractiveAuthenticationSuccessEvent 被调用发布认证成功事件。

  • AuthenticationSuccessHandler被调用,重定向登录前URL

3.2.1 进入 AbstractAuthenticationProcessingFilter

表单登录时,登录请求会进入到 UsernamePasswordAuthenticationFilter ,该过滤器会拦截浏览器提交的表单登录请求并进行身份认证。

UsernamePasswordAuthenticationFilter 继承自父类 AbstractAuthenticationProcessingFilterUsernamePasswordAuthenticationFilter 没有对父类的 AbstractAuthenticationProcessingFilterdoFilter 方法进行重写,故实际执行的是父类 AbstractAuthenticationProcessingFilterdoFilter 方法。

采用模版模式,根据不同的认证方式,执行不同子类的认证逻辑。

AbstractAuthenticationProcessingFilterdoFilter 方法几乎完成认证的所有流程:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {if (!requiresAuthentication(request, response)) {chain.doFilter(request, response);return;}try {Authentication authenticationResult = attemptAuthentication(request, response);if (authenticationResult == null) {// return immediately as subclass has indicated that it hasn't completedreturn;}this.sessionStrategy.onAuthentication(authenticationResult, request, response);// Authentication successif (this.continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}successfulAuthentication(request, response, chain, authenticationResult);}catch (InternalAuthenticationServiceException failed) {this.logger.error("An internal error occurred while trying to authenticate the user.", failed);unsuccessfulAuthentication(request, response, failed);}catch (AuthenticationException ex) {// Authentication failedunsuccessfulAuthentication(request, response, ex);}
}/*** Performs actual authentication.* <p>* The implementation should do one of the following:* <ol>* <li>Return a populated authentication token for the authenticated user, indicating* successful authentication</li>* <li>Return null, indicating that the authentication process is still in progress.* Before returning, the implementation should perform any additional work required to* complete the process.</li>* <li>Throw an <tt>AuthenticationException</tt> if the authentication process* fails</li>* </ol>* @param request from which to extract parameters and perform the authentication* @param response the response, which may be needed if the implementation has to do a* redirect as part of a multi-stage authentication process (such as OIDC).* @return the authenticated user token, or null if authentication is incomplete.* @throws AuthenticationException if authentication fails.*/
public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException, IOException, ServletException;/*** Default behaviour for successful authentication.* <ol>* <li>Sets the successful <tt>Authentication</tt> object on the* {@link SecurityContextHolder}</li>* <li>Informs the configured <tt>RememberMeServices</tt> of the successful login</li>* <li>Fires an {@link InteractiveAuthenticationSuccessEvent} via the configured* <tt>ApplicationEventPublisher</tt></li>* <li>Delegates additional behaviour to the* {@link AuthenticationSuccessHandler}.</li>* </ol>** Subclasses can override this method to continue the {@link FilterChain} after* successful authentication.* @param request* @param response* @param chain* @param authResult the object returned from the <tt>attemptAuthentication</tt>* method.* @throws IOException* @throws ServletException*/
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,Authentication authResult) throws IOException, ServletException {SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();context.setAuthentication(authResult);this.securityContextHolderStrategy.setContext(context);this.securityContextRepository.saveContext(context, request, response);if (this.logger.isDebugEnabled()) {this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));}this.rememberMeServices.loginSuccess(request, response, authResult);if (this.eventPublisher != null) {this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));}this.successHandler.onAuthenticationSuccess(request, response, authResult);
}/*** Default behaviour for unsuccessful authentication.* <ol>* <li>Clears the {@link SecurityContextHolder}</li>* <li>Stores the exception in the session (if it exists or* <tt>allowSesssionCreation</tt> is set to <tt>true</tt>)</li>* <li>Informs the configured <tt>RememberMeServices</tt> of the failed login</li>* <li>Delegates additional behaviour to the* {@link AuthenticationFailureHandler}.</li>* </ol>*/
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,AuthenticationException failed) throws IOException, ServletException {this.securityContextHolderStrategy.clearContext();this.logger.trace("Failed to process authentication request", failed);this.logger.trace("Cleared SecurityContextHolder");this.logger.trace("Handling authentication failure");this.rememberMeServices.loginFail(request, response);this.failureHandler.onAuthenticationFailure(request, response, failed);
}

3.2.2 进入 UsernamePasswordAuthenticationFilter

随后进入 UsernamePasswordAuthenticationFilterattemptAuthentication 方法,该方法会预构建认证对象 UsernamePasswordAuthenticationToken(未认证):

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {if (this.postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}String username = obtainUsername(request);username = (username != null) ? username.trim() : "";String password = obtainPassword(request);password = (password != null) ? password : "";UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,password);// Allow subclasses to set the "details" propertysetDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);
}

UsernamePasswordAuthenticationToken 刚创建时,包含输入的用户名、密码、客户端IPsessionID 等信息,这时是未认证状态:

在这里插入图片描述

ProviderManagerAuthenticationManager 的实现类,ProviderManager 遍历所有的认证提供者,DaoAuthenticationProvider 符合 Form 表单认证,调用其 authenticate 方法进行认证。

在这里插入图片描述

3.2.3 进入 DaoAuthenticationProvider

UsernamePasswordAuthenticationToken 类型的 Authentication 对象是由 DaoAuthenticationProvider 进行认证处理 ,和上文的 AbstractAuthenticationProcessingFilter 类似,也是采用模版模式,首先调用的是父类 AbstractUserDetailsAuthenticationProviderauthenticate 方法:

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports","Only UsernamePasswordAuthenticationToken is supported"));String username = determineUsername(authentication);boolean cacheWasUsed = true;UserDetails user = this.userCache.getUserFromCache(username);if (user == null) {cacheWasUsed = false;try {user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);}catch (UsernameNotFoundException ex) {this.logger.debug("Failed to find user '" + username + "'");if (!this.hideUserNotFoundExceptions) {throw ex;}throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");}try {this.preAuthenticationChecks.check(user);additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);}catch (AuthenticationException ex) {if (!cacheWasUsed) {throw ex;}// There was a problem, so try again after checking// we're using latest data (i.e. not from the cache)cacheWasUsed = false;user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);this.preAuthenticationChecks.check(user);additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);}this.postAuthenticationChecks.check(user);if (!cacheWasUsed) {this.userCache.putUserInCache(user);}Object principalToReturn = user;if (this.forcePrincipalAsString) {principalToReturn = user.getUsername();}return createSuccessAuthentication(principalToReturn, authentication, user);
}

其中有两个比较重要的点:

  • 调用子类 DaoAuthenticationProviderretrieveUser 方法,根据用户名获取用户信息
  • 调用子类 DaoAuthenticationProvideradditionalAuthenticationChecks 方法,校验密码

在校验密码成功后,AbstractUserDetailsAuthenticationProvider 会创建一个认证成功的 Authentication 对象:

protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,UserDetails user) {// Ensure we return the original credentials the user supplied,// so subsequent attempts are successful even with encoded passwords.// Also ensure we return the original getDetails(), so that future// authentication events after cache expiry contain the detailsUsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(principal,authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));result.setDetails(authentication.getDetails());this.logger.debug("Authenticated user");return result;
}

3.2.4 认证成功处理

3.2.4.1 会话策略处理

回到 3.2.1 中的 doFilter 方法进行认证成功的后续处理:

在这里插入图片描述

  • CsrfAuthenticationStrategy :它负责在执行认证请求之后,删除旧的令牌生成新的,确保每次请求之后,csrf-token 都得到更新。
  • ChangeSessionIdAuthenticationStrategy :主要是使用HttpServletRequest.changeSessionId()方法修改sessionID来防止会话固定攻击。
3.2.4.2 调用 successfulAuthentication

会话处理完成后,调用 successfulAuthentication 进行认证成功后续处理:

protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,Authentication authResult) throws IOException, ServletException {SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();context.setAuthentication(authResult);this.securityContextHolderStrategy.setContext(context);this.securityContextRepository.saveContext(context, request, response);if (this.logger.isDebugEnabled()) {this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));}this.rememberMeServices.loginSuccess(request, response, authResult);if (this.eventPublisher != null) {this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));}this.successHandler.onAuthenticationSuccess(request, response, authResult);
}

执行以下业务逻辑:

  • SecurityContextHolder 上存储认证信息。

  • RememberMeServices#loginSuccess 被调用。 如果未配置“记住我”,则为空操作。

  • ApplicationEventPublisher#InteractiveAuthenticationSuccessEvent 被调用发布认证成功事件。

  • AuthenticationSuccessHandler被调用,重定向登录前URL

    此时 AuthenticationSuccessHandler 的实现为 SavedRequestAwareAuthenticationSuccessHandler

3.2.5 认证失败处理

如果认证失败,比如密码错误,在 AbstractUserDetailsAuthenticationProviderauthenticate 方法中抛出以下异常信息:

org.springframework.security.authentication.BadCredentialsException: 用户名或密码错误

之后会进入到 AbstractAuthenticationProcessingFilter 失败处理逻辑中:

在这里插入图片描述

失败处理方法如下:

protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,AuthenticationException failed) throws IOException, ServletException {this.securityContextHolderStrategy.clearContext();this.logger.trace("Failed to process authentication request", failed);this.logger.trace("Cleared SecurityContextHolder");this.logger.trace("Handling authentication failure");this.rememberMeServices.loginFail(request, response);this.failureHandler.onAuthenticationFailure(request, response, failed);
}

执行以下业务逻辑:

  • 清理 SecurityContextHolder

  • RememberMeServices#loginFail被调用。 如果未配置“记住我”,则为空操作。

  • AuthenticationFailureHandler 被调用,重定向 /login?error 登录页面,显示错误信息 。

    此时 AuthenticationFailureHandler 的实现为 SimpleUrlAuthenticationFailureHandler

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/233723.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode—454.四数相加II【中等】

2023每日刷题&#xff08;六十四&#xff09; Leetcode—454.四数相加II 实现代码 class Solution { public:int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {unordered_map&l…

雷电4.0.50模拟器Android7.1.2安装xposed框架

官方论坛&#xff1a;https://xdaforums.com/t/official-xposed-for-lollipop-marshmallow-nougat-oreo-v90-beta3-2018-01-29.3034811/ Xposed 有分支 [EdXposed 和 LSPosed] 。 Edxposed框架现在支持android 8.0 - android 9.0 &#xff0c;如果是android 7.0或更早的版本&…

2023最新最全【Kali Linux】入门教程【从零基础入门到精通】附安装包

作为一名从事渗透测试的人员&#xff0c;不懂Kali Linux的话&#xff0c;就out了。它预装了数百种享誉盛名的渗透工具&#xff0c;使你可以更轻松地测试、破解以及进行与数字取证相关的任何其他工作。 今天给大家分享一套Kali Linux资料合集&#xff0c;包括12份Kali Linux渗透…

LED恒流调节器FP7126:引领LED照明和调光的新时代(调光电源、汽车大灯)

目录 一、FP7126概述 二、FP7126功能 三、应用领域 随着科技的进步&#xff0c;LED照明成为了当代照明产业的主力军。而在LED照明的核心技术中&#xff0c;恒流调节器是不可或缺的组成部分。今天&#xff0c;我将为大家介绍一款重要的恒流调节器FP7126&#xff0c;适用于LED…

从 Android 手机恢复删除的数据的10个有效工具

您是否曾经在 Android 手机上遇到过数据丢失的情况&#xff0c;即您拍摄的瞬间或其他数据意外丢失&#xff1f; 就我而言&#xff0c;我多次遇到过此类数据丢失的情况&#xff0c;相信我&#xff0c;没有什么比从手机中丢失所有重要数据更严重的了。这就像一场噩梦&#xff0c…

第二十一章 : Spring Boot 集成定时任务(一)

第二十一章 &#xff1a; Spring Boot 集成定时任务&#xff08;一&#xff09; 前言 本章知识点&#xff1a; 介绍使用Spring Boot内置的Scheduled注解来实现定时任务-单线程和多线程&#xff1b;以及介绍Quartz定时任务调度框架&#xff1a;简单定时调度器&#xff08;Simp…

Dubbo线程池

前言 Dubbo使用Netty作为网络调用框架&#xff0c;Netty是一个Reactor模型的框架&#xff0c;线程模型分为boss线程池和worker线程池&#xff0c;boss线程池负责监听、分配事件&#xff0c;worker线程池负责处理事件&#xff0c;简单说就是boss线程池负责hold请求&#xff0c;并…

【算法】选择排序

1、排序逻辑 选择排序逻辑&#xff1a;对数组中的数据&#xff0c;先假定一个最小的数据下标&#xff0c;然后进行循环寻找到最小数据的下标&#xff0c;放在第一层循环的最初始位置 例&#xff1a; 从0 ~ N-1 寻找到最小值&#xff0c;放在0位置 从1~N-1 寻找到最小值 &…

自定义IDEA代码补全插件

目标&#xff1a; 对于项目中的静态方法&#xff08;主要是各种工具类里的静态方法&#xff09;&#xff0c;可以在输入方法名时直接提示相关的静态方法&#xff0c;选中后自动补全代码&#xff0c;并导入静态类。 设计&#xff1a; 初步构想&#xff0c;用户选择要导入的文…

SearchWP WordPress高级网站内容搜索插件

点击阅读SearchWP WordPress高级网站内容搜索插件原文 SearchWP WordPress高级网站内容搜索插件是一个非常强大的工具&#xff0c;可以显着增强您网站的搜索功能。通过向网站访问者提供高度相关和精确的搜索结果&#xff0c;它可以有效地简化他们的搜索过程&#xff0c;促进发…

CentOS 8离线安装telnet

下载telnet rpm安装包&#xff0c;可从https://www.rpmfind.net/linux/rpm2html/search.php?querytelnet&submitSearch…&systemcentos&arch 根据自己的操作系统下载对应的包&#xff0c;这里以CentOS8为例,分别下载如下的rtp包 xinetd-2.3.15-24.el8.x86_64.rpm…

设计师必备的Figma可视化组件库资产已更新至 7.0版本

在当今数字化时代&#xff0c;数据量呈爆炸式增长&#xff0c;大屏可视化的主要程度越来越高&#xff0c;而大屏背后的设计师们面对的挑战也越来越多&#xff0c;其中之一就是大屏可视化设计项目中的重复性元素设计。这一过程不仅耗费时间&#xff0c;还明显降低了设计团队的生…

Qt6.5类库详解:QLineEdit

哈喽大家好&#xff0c;我是20YC小二&#xff01;欢迎关注(20YC编程)&#xff0c;现在有免费《C程序员》视频教程下载哦&#xff01; ~下面开始今天的分享内容~ 1. QLineEdit介绍 QLineEdit是一个单行文本编辑器&#xff0c;允许用户输入和编辑纯文本。它提供了许多有用的编辑…

SSH的交互原理(wireshark的分析)

SSH的交换原理&#xff08;wireshark篇&#xff09; 首先要想了解ssh的交换原理&#xff0c;必须要先了解他的加密方式&#xff0c;他的加密方式是对称加密&#xff0c;和公钥加密。什么意思呢&#xff1f; 首先我们向服务器发送一个请求&#xff0c;然后服务器会发给我们他的…

GitBook安装及使用——使用 Markdown 创建你自己的博客网站和电子书

目录 前言一、依赖环境二、gitbook安装使用1.安装 gitbook-cli2.安装 gitbook3.Gitbook初始化4.创建你的文章5.修改 SUMMARY.md 和 README.md6.编译生成静态网页7.运行以便在浏览器预览8.运行效果 前言 GitBook是一个命令行工具&#xff0c;用于使用 Markdown 构建漂亮的博客网…

【鉴权】JWT加密

目录 定义 官网 定义 JWT是JSON Web Token的缩写&#xff0c;是RFC7519规范。该规范目的是为了让客户端和服务端可靠的传递信息。 官网 JSON Web Tokens - jwt.io JWT是由三个部分组成&#xff0c;HMACSHA256( base64UrlEncode(header) "." base64UrlEncode(pa…

AWS 知识二:AWS同一个VPC下的ubuntu实例通过ldapsearch命令查询目录用户信息

前言&#xff1a; 前提&#xff1a;需要完成我的AWS 知识一创建一个成功运行的目录。 主要两个重要&#xff1a;1.本地windows如何通过SSH的方式连接到Ubuntu实例 2.ldapsearch命令的构成 一 &#xff0c;启动一个新的Ubuntu实例 1.创建一个ubuntu实例 具体创建实例步骤我就不…

vue el-date-picker中datetime类型对今天之后的日期包含时分禁用

vue el-date-picker中datetime类型对今天之后的日期包含时分禁用 目前对选择秒那一列未禁用 <template><div><el-date-pickerv-model"deactivateTime"type"datetime"format"yyyy-MM-dd HH:mm:ss"value-format"yyyy-MM-dd HH…

抖音直播间websocket礼物和弹幕消息推送可能出现重复的情况,解决办法

在抖音直播间里&#xff0c;通过websocket收到的礼物消息数据格式如下&#xff1a; {common: {method: WebcastGiftMessage,msgId: 7283420150152942632,roomId: 7283413007005207308,createTime: 1695803662805,isShowMsg: True,describe: 莎***:送给主播 1个入团卡,priority…

HarmonyOS4.0从零开始的开发教程17给您的应用添加通知

HarmonyOS&#xff08;十五&#xff09;给您的应用添加通知 通知介绍 通知旨在让用户以合适的方式及时获得有用的新消息&#xff0c;帮助用户高效地处理任务。应用可以通过通知接口发送通知消息&#xff0c;用户可以通过通知栏查看通知内容&#xff0c;也可以点击通知来打开应…