导读:本期聚焦于徐致远创作的《如何用 Spring Boot 整合 Spring Security 实现表单登录认证?》,敬请观看详情。一个常见的后端需求是让应用具备基于表单的用户名密码登录能力,Spring Boot 与 Spring Security 的组合可以快速完成这一目标。本文不绕弯子,直接给出整合步骤:先引入 spring-boot-starter-security 依赖,再通过 SecurityFilterChain 配置默认表单登录、登录页、成功与失败处理逻辑。核心要点包括自定义 UserDetailsService 从数据库加载用户、BCrypt 密码编码器校验凭证、关闭 CSRF 以方便测试、以及配置未认证请求跳转。文章提供完整可运行的配置类与控制器代码,并解释 Spring Security 过滤器链的认证流程。读完你能够独立搭建一个具备表单登录、权限校验和退出登录功能的 Spring Boot 应用。

在 Spring Boot 项目中,借助 Spring Security 的自动配置与链式 API,能够以较少的代码实现基于表单的用户名密码登录认证。本文将从依赖引入、安全配置、用户数据来源、密码加密、登录回调以及退出机制几个方面完整演示整合过程,帮助你把认证模块快速落地到实际项目中。

如何用 Spring Boot 整合 Spring Security 实现表单登录认证?

一、引入依赖与基础工程搭建

创建 Spring Boot 项目时,只需要在 pom.xml 中加入 spring-boot-starter-security 和 spring-boot-starter-web 两个核心依赖。前者会导入 Spring Security 的过滤器链、默认登录页以及基础安全组件,后者提供 MVC 控制器与页面渲染能力。如果使用 Gradle,同样添加对应依赖即可。

依赖配置如下所示。Spring Boot 的版本建议使用 2.7 或 3.x,二者在配置类写法上略有差异,本文以 Spring Security 5.7 之后的组件化配置方式为主,不再使用已过时的 WebSecurityConfigurerAdapter。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
</dependencies>

引入依赖后,直接启动应用,Spring Security 的自动配置会立即生效。此时访问任意接口都会被重定向到内置的登录页面,默认用户名为 user,密码会在启动日志中随机生成。这种默认行为适合快速体验,但离真实业务还有距离,需要自定义用户来源、密码编码方式和登录页面。

为了让应用有一个可访问的首页和受保护接口,可以创建一个简单控制器。示例中定义 / 和 /admin 两个路径,后续通过安全规则限制 /admin 必须登录后才能访问。

@RestController
public class HomeController {

    @GetMapping("/")
    public String home() {
        return "欢迎访问首页";
    }

    @GetMapping("/admin")
    public String admin() {
        return "管理员页面,只有登录用户才能看到";
    }
}

基础工程到此准备完毕。接下来的重点是把安全配置从默认行为切换为可定制逻辑,让表单登录真正对接业务用户数据。

二、SecurityFilterChain 配置与表单登录流程

Spring Security 的核心是过滤器链。每个请求都会经过一系列过滤器,其中 UsernamePasswordAuthenticationFilter 负责处理表单登录提交。当用户未认证时访问受保护资源,ExceptionTranslationFilter 会触发认证流程,将用户重定向到登录页;用户提交用户名和密码后,认证管理器会校验凭证,成功则把认证信息放入 SecurityContext,失败则回退到登录页并提示错误。

在配置类中,通过暴露 SecurityFilterChain Bean 来定义安全规则。常用配置包括放行静态资源、指定登录页地址、允许表单登录、设置登录处理 URL、放行登录接口以及配置退出登录行为。下面的示例演示了最精简但完整的表单登录配置。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/login", "/css/**", "/js/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .loginProcessingUrl("/login")
                .defaultSuccessUrl("/admin", true)
                .failureUrl("/login?error")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login?logout")
                .permitAll()
            );
        return http.build();
    }
}

上述配置中,authorizeHttpRequests 使用 lambda 表达式定义授权规则。requestMatchers 指定哪些请求可以被匿名访问,/admin 路径要求用户具有 ADMIN 角色。formLogin 部分设置自定义登录页为 /login,同时将登录表单提交地址也映射到 /login,这样控制器只需要提供 GET 登录页,POST 由 Spring Security 自动处理。defaultSuccessUrl 表示登录成功后默认跳转到 /admin,failureUrl 指定认证失败后重定向地址并携带 error 参数。

需要注意,自定义登录页后,如果登录页需要加载 CSS 或 JS,必须把这些静态资源路径加入 permitAll,否则浏览器在未认证状态下无法获取静态文件,导致页面样式丢失。另外,如果暂时不准备做 CSRF 令牌校验,可以在 http 配置中追加 csrf(csrf -> csrf.disable()),但生产环境建议保留并按模板输出令牌。

理解表单登录流程对排查问题很有帮助。当浏览器访问 /admin 时,由于当前没有认证信息,请求被转发到 /login 页面;用户填写表单提交到 /login,Spring Security 的 UsernamePasswordAuthenticationFilter 会读取 username 和 password 参数,构造 UsernamePasswordAuthenticationToken 并交给 AuthenticationManager。认证管理器调用我们提供的 UserDetailsService 获取用户信息,再用密码编码器比对密码。若比对成功,认证对象被放入 SecurityContextHolder,之后重定向到原始请求地址。

三、自定义 UserDetailsService 与密码加密

真实项目中用户通常存储在数据库,因此需要自定义 UserDetailsService,从持久层加载用户并返回 Spring Security 可识别的 UserDetails 对象。Spring Security 内置的 InMemoryUserDetailsManager 只适合演示或测试,不适合生产使用。实现 UserDetailsService 接口的 loadUserByUsername 方法即可完成自定义逻辑。

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserEntity user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));

        return org.springframework.security.core.userdetails.User
            .withUsername(user.getUsername())
            .password(user.getPassword())
            .roles(user.getRole())
            .disabled(!user.isEnabled())
            .build();
    }
}

在这个例子中,UserRepository 是 JPA 或 MyBatis 等持久层接口,负责根据用户名查询用户。UserEntity 是业务实体,包含用户名、密码、角色和启用状态。返回的 UserDetails 使用 Spring Security 提供的 User 构建器创建,其中 roles 方法会自动为角色添加 ROLE_ 前缀,所以数据库里角色字段可以只存 ADMIN 或 USER。

密码安全问题必须使用 PasswordEncoder。生产环境绝对不能明文存储密码,Spring Security 推荐 BCryptPasswordEncoder。配置时只需要在容器中注册一个 PasswordEncoder Bean,并在用户创建或注册时用相同编码器加密密码。如果数据库中的密码已经是 BCrypt 加密格式,登录比对过程会自动完成,不需要额外解码。

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

还有一种做法是使用 DelegatingPasswordEncoder,它支持多种编码格式,密码前缀可以标识算法,例如 {bcrypt} 开头。不过对于大多数单体应用或中小项目,直接使用 BCryptPasswordEncoder 足够,并且配置更简单。需要注意的是,如果数据库里存的是明文密码,而配置了 BCryptPasswordEncoder,登录时会因为格式不匹配而失败,因此初始化数据时必须先对密码做 BCrypt 加密。

在注册接口中,可以对用户提交的密码执行 passwordEncoder.encode(rawPassword),再把加密结果写入数据库。这样既能保护用户敏感信息,也能保证后续登录认证顺利进行。密码编码器还支持 matches 方法用于直接比对原始密码与加密密码,这在修改密码等业务场景非常有用。

四、登录成功、失败处理与退出登录

默认的登录成功和失败处理只能完成简单跳转,但很多业务需要在认证成功后记录日志、发放 token、更新最后登录时间,或者在失败时统计错误次数、锁定账户等。Spring Security 提供了 AuthenticationSuccessHandler 和 AuthenticationFailureHandler 接口,可以在表单登录配置中指定自定义处理器。

下面示例演示登录成功后返回 JSON 而不是重定向,适合前后端分离项目。在配置类的 formLogin 部分通过 successHandler 指定处理器。自定义处理器使用 ObjectMapper 输出 JSON 响应,并设置合适的 Content-Type。

@Component
public class JsonAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException {
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType("application/json;charset=UTF-8");
        Map<String, Object> data = new HashMap<>();
        data.put("code", 200);
        data.put("message", "登录成功");
        data.put("username", authentication.getName());
        response.getWriter().write(objectMapper.writeValueAsString(data));
    }
}

与之对应,失败处理器可以捕获 BadCredentialsException、LockedException 等异常,根据异常类型返回不同的提示信息。比如用户名不存在或密码错误时返回登录失败,账户被禁用时返回账户已锁定。这样可以提升用户体验并方便前端做差异化展示。

退出登录同样重要。Spring Security 的 logout 配置默认提供 /logout 退出接口,并且会使当前 Session 失效、清除 SecurityContext。在前后端分离场景下,可以自定义 LogoutSuccessHandler 返回 JSON。配置方式与登录处理器类似,使用 logoutSuccessHandler 方法即可。如果使用 JWT 无状态认证,退出登录主要在前端删除令牌,但表单登录仍然依赖 Session,因此服务端退出逻辑必须保留。

最后还需要注意表单页面本身。Spring Security 要求登录表单的 input name 属性必须为 username 和 password,提交方式为 POST,提交地址与 loginProcessingUrl 一致。如果使用 Thymeleaf 模板渲染页面,可以借助 Spring Security 的标签库显示认证信息,但核心还是标准的 HTML 表单。页面中如果启用了 CSRF,需要在表单里输出隐藏令牌,否则 POST 请求会被 CSRF 过滤器拦截。

五、运行验证与常见问题

完成以上配置后,启动应用并访问 http://localhost:8080/admin。如果未登录,浏览器会跳转到自定义的 /login 页面。输入数据库中的用户名和密码,登录成功后跳回 /admin,页面上显示管理员内容。退出登录后再次访问 /admin,会重新要求认证,说明安全规则已经生效。

常见问题之一是自定义登录页后静态资源 403 或页面样式丢失。解决方法是把静态资源路径加入 permitAll,同时确认安全配置类没有被重复加载。另一个问题是角色前缀不匹配,例如数据库角色为 ADMIN,但配置中写 hasRole("ADMIN") 实际上会匹配 ROLE_ADMIN,因此 roles 方法会自动加前缀,而 authorities 方法则不会,需要特别注意。

如果登录一直失败,首先检查密码是否在数据库中以 BCrypt 存储,其次确认 UserDetailsService 是否被 Spring 容器正确代理。也可以通过开启 debug 日志查看认证过程,在 application.properties 中设置 logging.level.org.springframework.security=DEBUG,观察过滤器链和认证决策,有助于快速定位问题。

总结来说,Spring Boot 整合 Spring Security 实现表单登录认证的关键在于理解过滤器链、正确配置 SecurityFilterChain、提供用户数据源和密码编码器。掌握这几个核心点后,无论是单体应用还是前后端分离项目,都能灵活调整认证策略,满足不同业务场景的安全需求。

Spring BootSpring Security表单登录认证修改时间:2026-08-20 15:06:21

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。