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.hqwc.cn/news/287204.html

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

相关文章

玩转大数据19:数据治理与元数据管理策略

随着大数据时代的到来&#xff0c;数据已经成为企业的重要资产。然而&#xff0c;如何有效地管理和利用这些数据&#xff0c;成为了一个亟待解决的问题。数据治理和元数据管理是解决这个问题的关键。 1.数据治理的概念和重要性 数据治理是指对数据进行全面、系统、规范的管理…

信创之国产浪潮电脑+统信UOS操作系统体验9:使用vscode构建的配置任务编译C++程序

☞ ░ 前往老猿Python博客 ░ https://blog.csdn.net/LaoYuanPython 一、引言 在《信创之国产浪潮电脑统信UOS操作系统体验2&#xff1a;安装visual studio code和cmake搭建C开发环镜》介绍了在国产浪潮电脑统信UOS操作系统中安装visual studio code和cmake搭建C开发环镜的过…

可视化开发平台哪个好?

“可视化开发平台哪个好&#xff1f;”&#xff0c;这是不少粉丝朋友经常询问我们的问题。作为专业的低代码技术平台服务商&#xff0c;流辰信息将全力以赴做好低代码平台的研发创新工作&#xff0c;持续为行业的数字化转型和流程化办公带来整套专业的数据平台方案服务。本文将…

七牛云API轻松集成,助力电商客服系统升级

七牛云&#xff1a;电商平台的无代码API集成解决方案 随着电子商务行业的蓬勃发展&#xff0c;电商平台正面临着数据管理和客户体验优化的巨大挑战。七牛云&#xff0c;作为业界领先的云服务提供商&#xff0c;针对这一需求推出了无代码API集成解决方案。这套方案旨在简化电商…

Big Data Tools插件(详细讲解安装,连接,包教包会!!!)

前言 ​ 最近有很多朋友都开始转行大数据竞赛了&#xff0c;大部分都是刚刚入门之类的&#xff0c;我自己是有一些基础的&#xff0c;玩过一段时间&#xff0c;最近很多好友学弟&#xff0c;都问过一个问题就是有没有什么类似于远程控制&#xff0c;或者图形化控制HDFS的插件或…

Python爬虫之两种urlencode编码发起post请求方式

背景 闲来无事想爬一下牛客网的校招薪资水平及城市分布&#xff0c;最后想做一个薪资水平分布的图表出来 于是发现牛客使用的是application/x-www-form-urlencoded的格式 测试 首先可以先用apipost等测试工具先测试一下是否需要cookie之类的&#xff0c;发现是不需要的&…

Unity中Shader测试常用的UGUI功能简介

文章目录 前言一、锚点1、锚点快捷修改位置2、使用Anchor Presets快捷修改3、Anchor Presets界面按下 Shift 可以快捷修改锚点和中心点位置4、Anchor Presets界面按下 Alt 可以快捷修改锚点位置、UI对象位置 和 长宽大小 二、Canvas画布1、UGUI中 Transform 变成了 Rect Transf…

【LeetCode刷题】-- 246.中心对称数

246.中心对称数 class Solution {public boolean isStrobogrammatic(String num) {HashMap map new HashMap();map.put(6,9);map.put(8,8);map.put(1,1);map.put(9,6);map.put(0,0);int n num.length();for(int i 0; i < n ;i){//如果字符串中包含不可翻转的字符&#xf…

创新蓄势!安全狗多项技术获颁专利

近日&#xff0c;安全狗《一种网络安全监测方法、终端设备及存储介质》、《一种恶意进程风险等级评估方法、终端设备及存储介质》等专利顺利通过了国家知识产权局的相关审核认证&#xff0c;并获得了发明专利证书。 厦门服云信息科技有限公司&#xff08;品牌名&#xff1a;安…

斐波那契的平方、立方问题——考虑几何立体意义(数形结合法):P9510

https://www.luogu.com.cn/problem/P9510 关于斐波那契和的平方&#xff0c;其实就是正方形的面积和&#xff1a; 也就是 f ( i ) ∗ f ( i 1 ) f(i)*f(i1) f(i)∗f(i1) 我们现在要求立方&#xff0c;但我们可以可以发现红色部分的结果是一样的&#xff1a; 直接三条棱表示…

多维时序 | MATLAB实现KOA-CNN-BiGRU-Multihead-Attention多头注意力机制多变量时间序列预测

多维时序 | MATLAB实现KOA-CNN-BiGRU-Multihead-Attention多头注意力机制多变量时间序列预测 目录 多维时序 | MATLAB实现KOA-CNN-BiGRU-Multihead-Attention多头注意力机制多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLAB实现KOA-CNN-B…

【Java】网络编程-UDP字典服务器客户端简单代码编写

上文讲了UDP回响服务器客户端简单代码编写 本文将讲述UDP字典服务器客户端简单代码编写。所谓回显&#xff0c;就是指客户端向服务器发送一个报文&#xff0c;从服务器那里得到一条一模一样的回响报文 而我们的字典功能呢&#xff0c;则是实现了输入中文&#xff0c;得到对应…