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

一、引入依赖与基础工程搭建
创建 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