在基于 Spring Security6 构建的 Web 应用中,用户认证是保障系统安全的重要环节。而登录成功后的处理逻辑,直接影响着用户体验和业务流程。
默认情况下,Spring Security6 会将用户重定向到一个固定的默认成功页面,但在实际项目中,我们往往需要根据用户角色、登录来源等因素,实现更灵活的登录成功处理。
下面将介绍如何自定义登录成功处理:
在 Spring Security6 的默认配置下,当用户通过身份验证后,会被重定向到 defaultSuccessUrl 指定的页面。如下:
package com.hxstrive.spring_security.config; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.ForwardAuthenticationFailureHandler; import java.io.IOException; import static org.springframework.security.config.Customizer.withDefaults; /** * Spring Security 配置类 * @author hxstrive.com */ @Configuration public class SecurityConfig { // 省略 UserDetailsService 和 PasswordEncoder 的配置... @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { System.out.println("securityFilterChain()"); // csrf 设置 // csrfCustomizer.ignoringRequestMatchers(*) 表示所有请求地址都不使用 csrf http.csrf(csrfCustomizer -> csrfCustomizer.ignoringRequestMatchers("*")) // authorize.anyRequest().authenticated() 表示所有的请求都需要进行身份验证。 .authorizeHttpRequests(authorize -> authorize // 配置登陆失败页面访问不需要鉴权,如果不配置,不会跳转到登录失败页面 .requestMatchers("/view/fail").permitAll() .anyRequest().authenticated()) // formLogin() 用于配置表单登录功能 .formLogin(e -> e.loginProcessingUrl("/login") // 配置处理登陆请求到地址 // 自定义登陆页面地址 .loginPage("/view/login") // 自定义登陆成功页面 .defaultSuccessUrl("/view/success") // 登陆失败页面 .failureUrl("/view/fail") .permitAll()) // httpBasic() 启用 HTTP 基本认证,withDefaults() 表示使用默认的 HTTP 基本认证配置。 .httpBasic(withDefaults()); return http.build(); } }
上述配置中,当用户登录成功后,会被重定向到 /view/success 页面。然而,这种简单的重定向方式在复杂业务场景下存在局限性,如无法根据用户角色跳转到不同页面、记录用户登录信息等等。
在 Spring Security6 中,successForwardUrl()、defaultSuccessUrl() 和 defaultSuccessUrl(..., true) 是用于处理登录成功后跳转逻辑的三个重要方法。看看下面三行代码,它们之间有什么区别呢?
successForwardUrl("/view/success") defaultSuccessUrl("/view/success") defaultSuccessUrl("/view/success", true)
登录成功后,使用服务器端转发(RequestDispatcher.forward)到指定的 URL。浏览器地址栏保持不变(仍显示登录页的 URL)。转发在服务器内部完成,不会创建新的 HTTP 请求,因此原请求的属性(如 request.setAttribute 设置的数据)会被保留。
适用于需要在登录成功后直接渲染视图,而不希望用户感知到 URL 变化的场景。
源码如下:
/** * 转发认证成功处理器 * @param forwardUrl 成功时的目标URL * @return the {@link FormLoginConfigurer} for additional customization */ public FormLoginConfigurer<H> successForwardUrl(String forwardUrl) { successHandler(new ForwardAuthenticationSuccessHandler(forwardUrl)); return this; }
登录成功后,使用 HTTP 重定向(302 状态码)到指定的 URL。浏览器地址栏会显示新的 URL(/view/success)。重定向会创建新的 HTTP 请求,原请求的属性(如表单数据)会丢失。
如果用户直接访问登录页(未被拦截的情况下),登录成功后会跳转到 defaultSuccessUrl。
如果用户是因为访问受保护资源被重定向到登录页,登录成功后会回到原请求的 URL(即“记住原请求”)。
源码如下:
/** * 指定如果用户在成功认证之前未访问过安全页面,成功认证后将被重定向到何处。 * 这是调用{@link #defaultSuccessUrl(String, boolean)}的快捷方式。 * @param defaultSuccessUrl 默认成功URL * @return the {@link FormLoginConfigurer} for additional customization */ public final T defaultSuccessUrl(String defaultSuccessUrl) { return defaultSuccessUrl(defaultSuccessUrl, false); }
登录成功后,强制使用 HTTP 重定向 到指定的 URL,忽略原请求。无论用户是如何到达登录页的,登录成功后都会固定跳转到 /view/success。常用于需要统一登录后跳转逻辑的场景(如管理后台)。
源码如下:
/** * 指定如果用户在成功认证后尚未访问过安全页面,或者`alwaysUse`为`true`, * 将用户重定向到何处。 * 这是调用`{@link #successHandler(AuthenticationSuccessHandler)}`的快捷方式。 * @param defaultSuccessUrl 默认成功URL * @param alwaysUse 如果在身份验证后,无论之前是否访问过受保护页面, * 都应使用{@code defaultSuccessUrl},则为true * @return the {@link FormLoginConfigurer} for additional customization */ public final T defaultSuccessUrl(String defaultSuccessUrl, boolean alwaysUse) { SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler(); handler.setDefaultTargetUrl(defaultSuccessUrl); handler.setAlwaysUseDefaultTargetUrl(alwaysUse); this.defaultSuccessHandler = handler; return successHandler(handler); }
下面通过一个表格,清晰说明它们的区别:
方法 | 跳转方式 | 地址栏变化 | 原请求属性 | 是否忽略原请求 |
defaultSuccessUrl("/path") | 重定向(302) | 改变 | 丢失 | 否(默认) |
defaultSuccessUrl("/path", true) | 重定向(302) | 改变 | 丢失 | 是(强制跳转) |
successForwardUrl("/path") | 服务器转发 | 不变 | 保留 | 是 |
它们到适用场景:
defaultSuccessUrl(默认):适用于用户可能从多个入口登录,需要回到原请求页面的场景(如电商网站)。
defaultSuccessUrl(..., true):适用于统一登录后体验的场景(如管理系统,所有用户登录后固定跳转到控制台)。
successForwardUrl:适用于需要在登录成功后继续处理原请求数据的场景(如表单提交后登录,登录成功后继续处理表单)。
要实现自定义登录成功处理,我们需要创建一个实现 AuthenticationSuccessHandler 接口的类,并重写onAuthenticationSuccess() 方法。然后,将 AuthenticationSuccessHandler 实现类的实例传递给 successHandler() 方法,如下:
/** * 指定要使用的{@link AuthenticationSuccessHandler}。默认值为 * {@link SavedRequestAwareAuthenticationSuccessHandler},且未设置其他属性。 * * @param successHandler {@link AuthenticationSuccessHandler}(认证成功处理器) * @return the {@link FormLoginConfigurer} for additional customization */ public final T successHandler(AuthenticationSuccessHandler successHandler) { this.successHandler = successHandler; return getSelf(); }
用户需要在 AuthenticationSuccessHandler 的 onAuthenticationSuccess() 方法中实现用户认证成功后的自定义逻辑。
示例:我们通过匿名内部类来实现,如下:
http.csrf(csrfCustomizer -> csrfCustomizer.ignoringRequestMatchers("*")) // authorize.anyRequest().authenticated() 表示所有的请求都需要进行身份验证。 .authorizeHttpRequests(authorize -> authorize // 配置登陆失败页面访问不需要鉴权,如果不配置,不会跳转到登录失败页面 .requestMatchers("/view/fail").permitAll() .anyRequest().authenticated()) // formLogin() 用于配置表单登录功能 .formLogin(e -> e.loginProcessingUrl("/login") // 配置处理登陆请求到地址 // 自定义登陆页面地址 .loginPage("/view/login") // 自定义登陆成功页面【看这里】 .successHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("onAuthenticationSuccess()"); response.sendRedirect("/view/success"); } }) // 登陆失败页面 .failureUrl("/view/fail") .permitAll()) // httpBasic() 启用 HTTP 基本认证,withDefaults() 表示使用默认的 HTTP 基本认证配置。 .httpBasic(withDefaults());
在上述代码中,onAuthenticationSuccess() 方法接收 HttpServletRequest、HttpServletResponse 和 Authentication 对象。通过 Authentication 对象可以获取当前登录用户的信息,如用户名、角色等。这里只是简单打印了登录成功信息,并将用户重定向到 /view/success 页面,实际项目中可以根据业务需求进行更复杂的处理。
在实际应用中,常常需要根据用户角色进行不同的登录成功处理。例如,管理员登录后跳转到管理页面,普通用户登录后跳转到个人中心页面。我们可以在自定义登录成功处理器中实现这一逻辑。如下:
package com.hxstrive.spring_security.customer; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import java.io.IOException; import java.util.Collection; public class RoleBasedAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { String username = authentication.getName(); Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); // 判断用户是否拥有 ROLE_ADMIN 角色 // 如果有,表示是管理员 boolean isAdmin = authorities.stream() .anyMatch(a -> "ROLE_ADMIN".equals(a.getAuthority())); if (isAdmin) { // 管理员 response.sendRedirect(request.getContextPath() + "/admin/dashboard"); } else { // 非管理员 response.sendRedirect(request.getContextPath() + "/user/profile"); } // 记录日志信息 System.out.println("用户 " + username + " 登录成功,重定向到对应页面"); } }
在上述代码中,通过 authentication.getAuthorities() 获取用户的权限集合,然后判断用户是否具有 ROLE_ADMIN 角色,根据角色不同将用户重定向到不同的页面。
使用 successForwardUrl() 时,表示成功后转发请求到地址。内部是通过 successHandler() 方法控制成功后交给哪个类进行处理,如下图:
源码中,ForwardAuthenticationSuccessHandler 类到内部就是最简单的请求转发。注意,由于使用的是请求转发,当遇到需要跳转到站外或在前后端分离的项目中就无法使用了。源码如下:
如果你之前使用过 Servlet,是不是很熟悉。
使用 defaultSuccessUrl() 时,表示成功后重定向到地址。内部也是通过 successHandler() 方法控制成功后交给哪个类进行处理,如下图:
SavedRequestAwareAuthenticationSuccessHandler 类的部分源码如下:
到这里,源码分析完了,下一章将介绍 Spring Security 自定义登陆失败处理……