第三章 自定义登录页与表单认证
本章将介绍如何自定义登录页面,配置登录成功/失败后的跳转逻辑,告别框架默认的登录页。
在上一篇文章中,我们实现了数据库认证,但登录页面仍然是框架自动生成的默认页面。在实际项目中,我们需要使用自己的登录页面,并配置个性化的跳转逻辑。本篇文章将详细介绍相关配置。
一、问题切入
第二章中我们使用的仍然是框架默认的登录页面:
- 样式千篇一律,无法满足项目 UI 设计需求
- 无法自定义登录成功后的跳转逻辑
- 无法在登录页添加验证码、社交登录等功能
- 无法获取登录失败的具体错误信息
我们需要一个可定制的登录解决方案。
二、解决方案:表单认证配置
在 SecurityConfig 中使用 .formLogin() 配置自定义登录页:
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) {
return http
.formLogin(form -> form
.loginProcessingUrl("/login") // 表单提交地址
.loginPage("/tologin") // 登录页URL
.defaultSuccessUrl("/", true) // 登录成功跳转
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/tologin").permitAll() // 无需认证
.anyRequest().authenticated() // 其他需认证
)
.build();
}
}
说明:你的
ss03当前代码中是loginProcessingUrl("/login"),而ss04已改为"/user/login"。两者都可以,但必须和前端表单action保持一致。
三、原理阐释:SecurityFilterChain
3.1 安全过滤链
Spring Security 的核心是 SecurityFilterChain,它由多个 Filter 组成:
请求 → SecurityContextPersistenceFilter
→ HeaderWriterFilter
→LogoutFilter
→ UsernamePasswordAuthenticationFilter ← 这里处理登录
→ DefaultLoginPageGeneratingFilter
→ DefaultLogoutPageGeneratingFilter
→ CsrfFilter
→ RequestCacheContextFilter
→ AuthorizationFilter ← 这里检查权限
→ ...
每个 Filter 只负责自己的职责,多个 Filter 按照顺序执行,形成责任链。
3.2 配置参数详解
| 配置项 | 说明 |
|---|---|
| loginProcessingUrl | 表单提交的目标URL,需与前端表单 action 一致 |
| loginPage | 自定义登录页URL,认证失败时自动重定向至此 |
| defaultSuccessUrl | 登录成功后强制跳转的URL |
| successForwardUrl | 登录成功后转发到指定 URL |
| failureUrl | 登录失败后跳转的 URL |
| permitAll() | 指定路径无需认证即可访问 |
| authenticated() | 其他所有请求需要认证 |
3.3 Lambda 配置风格
本章采用 Spring Security 7.x 推荐的 Lambda 配置风格:
// 旧版风格
http
.formLogin()
.loginProcessingUrl("/login")
.loginPage("/tologin")
.and()
.authorizeHttpRequests()
.antMatchers("/tologin").permitAll()
.anyRequest().authenticated();
// Lambda 风格(推荐)
http
.formLogin(form -> form
.loginProcessingUrl("/login")
.loginPage("/tologin")
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/tologin").permitAll()
.anyRequest().authenticated()
);
Lambda 风格更简洁、可读性更高,是 Spring Security 7.x 推荐的写法。
四、控制器实现
@Controller
public class UserController {
// 访问根路径
@ResponseBody
@RequestMapping("/")
public String index() {
return "欢迎回来";
}
// 跳转登录页
@RequestMapping("/tologin")
public String toLogin() {
return "login"; // 对应 src/main/resources/templates/login.html
}
}
五、前端登录页
使用 Thymeleaf 模板引擎创建登录页面 login.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="username"> <br/>
密码:<input type="password" name="password"> <br/>
<!-- CSRF Token:防止跨站请求伪造 -->
<input type="hidden" name="_csrf" th:value="${_csrf.token}">
<input type="submit" value="登录">
</form>
</body>
</html>
关键点:
- 表单
action="/login"必须与 SecurityConfig 中的loginProcessingUrl保持一致- 必须包含 CSRF Token
th:value="${_csrf.token}",否则登录请求会被拒绝
5.1 深入理解这行隐藏域
<input type="hidden" name="_csrf" th:value="${_csrf.token}">
这行代码不是“可选增强”,而是开启 CSRF 防护时,POST 表单能通过校验的关键。
(1)它在做什么
name="_csrf":告诉 Spring Security 这是 CSRF 参数(默认参数名就是_csrf)th:value="${_csrf.token}":Thymeleaf 在服务端渲染页面时,把当前请求对应的 token 值写入这个隐藏域
用户提交表单时,这个隐藏字段会和 username/password 一起提交到后端。
(2)为什么需要它
CSRF(跨站请求伪造)的典型攻击方式是:
- 用户已在你的站点登录(浏览器里有有效会话 Cookie)
- 用户访问了攻击者网站
- 攻击者网站偷偷向你的站点发起一个“状态变更请求”(比如转账、改密码、发帖)
因为浏览器会自动带上 Cookie,后端如果只认 Cookie,就可能误以为这是用户本人操作。
Spring Security 的做法是:除了 Cookie,再要求请求里必须带一个后端签发的随机 token。攻击者站点拿不到这个 token,所以伪造请求通常会失败。
(3)后端如何校验
在过滤器链里,CsrfFilter 会检查:
- 请求是否属于需要保护的方法(POST/PUT/PATCH/DELETE)
- 请求参数或请求头里是否携带 token
- 这个 token 是否与服务端保存的 token 匹配
不匹配就拒绝,请求通常返回 403。
(4)为什么 GET 一般不需要带
Spring Security 默认认为 GET/HEAD/OPTIONS/TRACE 是“只读”请求,不会修改服务端状态,所以不强制 CSRF token。会修改状态的请求(尤其 POST)才是重点防护对象。
(5)和前后端分离场景的区别
你这个 ss03 是服务端模板渲染(Thymeleaf),所以直接在表单放隐藏域是最自然的方式。
如果是前后端分离(SPA),通常会:
- 后端把 CSRF token 放在响应头或 Cookie
- 前端在后续 AJAX 请求头里回传(如
X-CSRF-TOKEN)
(6)如果你去掉这行会怎样
- 登录表单使用 POST 提交时,
CsrfFilter校验失败 - 常见现象:登录失败、403、或者一直跳转回登录页
所以这行隐藏域要和你的 POST 表单一起保留。
六、补充依赖
如需使用 Thymeleaf 模板引擎,需添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
七、核心概念总结
| 配置 | 说明 |
|---|---|
| formLogin() | 启用表单认证,替代 HTTP Basic 认证 |
| loginProcessingUrl() | 指定表单提交地址 |
| loginPage() | 指定自定义登录页 |
| Lambda 配置风格 | Spring Security 7.x 推荐写法 |
| CSRF Token | 防止跨站请求伪造 |
八、总结
本篇文章介绍了以下核心内容:
- SecurityFilterChain:安全过滤器链,多个 Filter 按顺序执行
- formLogin():启用表单认证
- loginProcessingUrl():指定表单提交地址,需与前端一致
- loginPage():指定自定义登录页,支持任何视图技术
- Lambda 配置风格:Spring Security 7.x 推荐写法
- CSRF 防护:表单必须包含 CSRF Token
下一篇文章我们将介绍验证码功能的实现。
编辑者:Flittly
更新时间:2026年4月