在基于 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 自定义登陆失败处理……