SpringSecurity实战教程(二)表单登录定制到处理逻辑的深度改造

1711 字
9 分钟
SpringSecurity实战教程(二)表单登录定制到处理逻辑的深度改造

前言#

通过上一章节的讲解,相信大家已经认识了Spring Security安全框架。在我们创建的第一个演示项目中,相信大家也发现了默认表单登录的局限性。Spring Security默认提供的登录页虽然快速可用,但存在三大问题:

  • 界面风格与业务系统不匹配
  • 登录成功/失败处理逻辑固定
  • 缺乏扩展能力(如验证码、多因子认证)

本章节我们将对Spring Security默认表单进行登录定制,并深度改造处理逻辑。


改造准备#

在之前的Maven项目中创建第二个子模块,命名为login-spring-security。由于需要自定义登录页,还需要引入thymeleaf模板框架:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

完整的Maven项目结构如下:

项目结构
项目结构


开始登录页改造#

创建自定义登录页#

首先,我们需要自定义一个带验证码的登录页。在resources/templates目录下创建login.html

src/main/resources/templates/login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>企业级登录系统</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container d-flex justify-content-center align-items-center vh-100">
<div class="w-100" style="max-width: 400px;">
<div class="card">
<div class="card-body">
<h2 class="card-title text-center mb-4">登录</h2>
<form th:action="@{/login}" method="post">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" name="username" id="username" placeholder="请输入用户名">
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control" name="password" id="password" placeholder="请输入密码">
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">登录</button>
</div>
<p class="mt-3 text-center"><a href="#">忘记密码?</a></p>
</form>
</div>
</div>
</div>
</div>
</body>
</html>

创建默认首页#

添加一个默认首页index.html,显示登出按钮:

src/main/resources/templates/index.html
<html xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>企业级登录系统</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<h1>Hello Security</h1>
<!-- 测试过程不需要关闭csrf防护 -->
<form th:action="@{/login}" method="post">
<button type="submit" class="btn btn-primary">Log Out</button>
</form>
<!-- 测试过程需要关闭csrf防护 否则404 -->
<a th:href="@{/logout}">Log Out</a>
</body>
</html>

添加Controller配置#

添加Controller配置首页以及登录页:

@Controller
public class DemoTowController {
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/")
public String index() {
return "index";
}
}

配置Spring Security#

最后对Spring Security进行配置:

@Configuration
public class BasicSecurityConfig {
// 配置安全策略
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login") // 自定义登录页路径
.permitAll() // 不需要对login认证
)
.logout(withDefaults())
.csrf(csrf -> csrf.disable()); // 关闭csrf防护
return http.build();
}
}

测试效果#

测试访问默认主页,由于主页被拦截会自动跳转到login登录页:

登录页
登录页

输入正确用户名密码后,自动返回主页,点击登出按钮自动回到登录页:

首页
首页

特别说明:

注意登录页以及主页登出,action采用@{}生成URL,Spring Security会自动帮我们生成name为 _csrf 的隐藏表单,作用于csrf防护:

CSRF隐藏字段
CSRF隐藏字段

如果登出页是 a 连接形式,为了保证登出不会出现 404 的问题:

  1. 先关闭 csrf 防护:http.csrf(csrf -> csrf.disable())
  2. 登出按钮使用表单方式:th:action="@{/logout}"

自定义用户名密码#

到这里有小伙伴又要问了,每次密码都是Spring Security自动生成的UUID,能否自定义用户名密码?答案是肯定的。Spring Security提供了在Spring Boot配置文件设置用户密码的功能:

application.yml
spring:
security:
user:
name: admin
password: admin

登录成功/失败跳转问题#

通过上述代码,可以看到登录成功后默认返回系统主页(index.html)。但业务需求可能需要跳转到别的页面,如何配置?

Spring Security配置类中 formLogin 提供了两个参数 defaultSuccessUrlfailureUrl 方便我们进行配置:

http.formLogin(form -> form
.loginPage("/login") // 自定义登录页路径
.defaultSuccessUrl("/home", true) // 登录成功后跳转路径
.failureUrl("/login?error=true") // 登录失败后跳转路径
.permitAll() // 不需要对login认证
)

自定义登出#

登出配置和登录基本相同:

http.logout(logout -> logout
.logoutUrl("/logout") // 自定义登出URL
.logoutSuccessUrl("/login?logout") // 登出成功跳转
)

前后端分离适配方案#

上述案例针对的是前后端在一个整体中的情况。针对现在前后端分离的项目,我们如何进行改造?需要处理以下问题:

  • 用户登录成功返回成功JSON / 失败返回对应JSON
  • 用户登出成功返回成功JSON / 失败返回对应JSON

原理说明#

首先引入官方介绍图如下:

认证处理器流程
认证处理器流程

在身份认证管理器AuthenticationManager中,有两个结果:SuccessFailure,最终交给AuthenticationSuccessHandler以及AuthenticationFailureHandler处理器处理。

简单总结:

  • 登录成功调用:AuthenticationSuccessHandler
  • 登录失败调用:AuthenticationFailureHandler

自定义处理器实现#

通过上面的讲解,我们只需要自定义这两个处理器即可:

@Configuration
public class BasicSecurityConfig {
// 配置安全策略
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/ajaxLogin").permitAll() // ajax登录页不需要认证
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login") // 自定义登录页路径
.successHandler(loginSuccessHandler())
.failureHandler(loginFailureHandler())
.permitAll() // 不需要对login认证
)
.logout(withDefaults())
.csrf(csrf -> csrf.disable()); // 关闭csrf防护
return http.build();
}
// 自定义登录成功处理器
@Bean
public AuthenticationSuccessHandler loginSuccessHandler() {
return (request, response, authentication) -> {
if (isAjaxRequest(request)) {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write("{\"code\":200, \"message\":\"认证成功\"}");
} else {
response.sendRedirect("/");
}
};
}
// 自定义登录失败处理器
@Bean
public AuthenticationFailureHandler loginFailureHandler() {
return (request, response, exception) -> {
if (isAjaxRequest(request)) {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write("{\"code\":401, \"message\":\"认证失败\"}");
} else {
response.sendRedirect("/login?error=true");
}
};
}
// 判断是否ajax请求
public boolean isAjaxRequest(HttpServletRequest request) {
String xRequestedWith = request.getHeader("X-Requested-With");
return "XMLHttpRequest".equals(xRequestedWith);
}
}

创建AJAX登录页#

新增一个ajaxLogin.html,使用ajax发送请求(为了测试方便,这里简单创建一个,不使用VUE等工程了):

src/main/resources/templates/ajaxLogin.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>企业级登录系统</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
</head>
<body>
<div class="container d-flex justify-content-center align-items-center vh-100">
<div class="w-100" style="max-width: 400px;">
<div class="card">
<div class="card-body">
<h2 class="card-title text-center mb-4">登录</h2>
<form>
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" name="username" id="username" placeholder="请输入用户名">
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control" name="password" id="password" placeholder="请输入密码">
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">登录</button>
</div>
<p class="mt-3 text-center"><a href="#">忘记密码?</a></p>
</form>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('form').submit(function (event) {
event.preventDefault();
var username = $('#username').val();
var password = $('#password').val();
$.ajax({
type: 'POST',
url: '/login',
data: {
username: username,
password: password
},
success: function (response) {
console.log(response)
if(response.code == 200){
window.location.href = '/';
}
}
})
})
})
</script>
</body>
</html>

添加Controller路由#

在Controller中追加页面展示:

@GetMapping("/ajaxLogin")
public String ajaxLogin() {
return "ajaxLogin";
}

测试效果#

最后启动Spring Boot项目,访问/ajaxLogin登录页,测试输入正确和不正确的账号密码,并观察浏览器控制台输出:

AJAX登录测试
AJAX登录测试


结语#

本章节介绍了如何通过Spring Security实现从配置自定义登录页面、表单登录处理逻辑的配置,并简单模拟了前后端分离的适配方案。

在接下来的章节中,我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术的方方面面。欢迎继续关注!

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
SpringSecurity实战教程(二)表单登录定制到处理逻辑的深度改造
https://fanrich.eu.org/posts/程技/java/springsecurity实战教程/二表单登录定制到处理逻辑的深度改造/
作者
richfan
发布于
2026-03-02
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
richfan
Hello, I'm richfan.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
分类
标签
站点统计
文章
779
分类
8
标签
138
总字数
3,267,679
运行时长
0
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.13.5
文章许可
CC BY-NC-SA 4.0

文章目录