Appearance
SpringMVC教程 - 20 登录功能和访问拦截(传统项目)
我们的教程到这里要结束了。
最后我们实现一个简单的 demo,实现:登录 + 注册 + 一个学生列表功能,学生列表在上一个章节已经实现了,现在还需要实现如下功能:
- 实现用户注册功能;
- 实现用户登录功能;
- 登录后跳转到学生列表页面;
- 如果没有登录,就通过 URL 访问学生列表,则自动跳转到登录页面。
这一章需要在 18-全注解开发、19-SSM整合 两个章节的基础上继续完成。
20.1 创建数据库表
首先创建数据库表,用户表,用来注册和登录。
sql
CREATE TABLE `tb_user` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(50) NULL comment '用户名',
`password` VARCHAR(50) NULL comment '密码',
`create_time` DATETIME NULL comment '创建时间',
`update_time` DATETIME NULL comment '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;20.2 实体类
创建用户实体类 User.java,与数据库表对应:
java
package com.foooor.helloweb.pojo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Integer id;
private String username;
private String password;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}- 如果之前已经有了 User 类,则修改一下字段。
20.3 Mapper接口
创建用户Mapper接口 UserMapper.java:
java
package com.foooor.helloweb.mapper;
import com.foooor.helloweb.pojo.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
/**
* 根据用户名查询用户
*/
@Select("SELECT * FROM tb_user WHERE username = #{username}")
User findByUsername(String username);
/**
* 注册用户
*/
@Insert("INSERT INTO tb_user (username, password, create_time, update_time) VALUES (#{username}, #{password}, NOW(), NOW())")
int insert(User user);
}- 一个根据用户名查询用户,在登录的时候使用;
- 一个插入用户,在注册的时候使用;
20.4 工具类
添加MD5密码加密工具类 MD5Util.java ,工具类提供一个加密方法,主要用来对密码进行加密。
在注册的时候页面传递密码到后端,后端使用加密算法对密码进行加密后,然后保存到数据库。在登录的时候,页面传递密码到后端,使用同样的方式将密码加密,加密后,在和数据库的数据进行比较(因为数据库的也是加密的),如果相同,则说明密码正确。
java
package com.foooor.helloweb.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Util {
/**
* 对字符串进行MD5加密
*/
public static String encrypt(String input) {
try {
// 创建MD5消息摘要实例
MessageDigest md = MessageDigest.getInstance("MD5");
// 更新摘要
md.update(input.getBytes());
// 获取加密后的字节数组
byte[] byteData = md.digest();
// 转换为十六进制字符串
StringBuilder sb = new StringBuilder();
for (byte b : byteData) {
sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5加密失败", e);
}
}
}- 这里使用最简单的 MD5 加密算法,准确的说 MD5 并不是一个加密算法,因为加密意味着解密,能解密才是加密算法。而 MD5 是不可逆的,是一个摘要算法,通过算法对内容提取摘要得到的一个 32 个字符(16个字节)的16进制字符串结果。对于一个很大的文件也可以计算一个 MD5 码,我们不可能通过一个 MD5 码反推得到一个很大的文件,那么这将是多么牛逼的压缩算法,可惜 MD5 是不可逆的。有很多网站说破解 MD5,其实是暴力破解,也就是给一个 MD5 码看一下数据库有没有,数据库存储了很多字符串对应的 MD5 码,然后去数据库查一下有没有,你搞一个文件的 MD5 码给他,一查一个不吱声。
20.5 Service层
创建用户服务接口IUserService.java:
java
package com.foooor.helloweb.service;
import com.foooor.helloweb.pojo.User;
public interface IUserService {
/**
* 用户注册
*/
boolean register(User user);
/**
* 用户登录
*/
User login(String username, String password);
}- 提供一个注册方法和一个登录方法。
创建用户服务实现类 UserServiceImpl.java:
java
package com.foooor.helloweb.service.impl;
import com.foooor.helloweb.common.BizException;
import com.foooor.helloweb.common.ErrorCode;
import com.foooor.helloweb.mapper.UserMapper;
import com.foooor.helloweb.pojo.User;
import com.foooor.helloweb.service.IUserService;
import com.foooor.helloweb.util.MD5Util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@Transactional
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
@Override
public boolean register(User user) {
User dbUser = userMapper.findByUsername(user.getUsername());
// 检查用户名是否已存在
if (dbUser != null) {
log.warn("用户名已存在");
throw new BizException(ErrorCode.USER_REGISTERED);
}
// 对密码进行加密处理
String encryptedPassword = MD5Util.encrypt(user.getPassword());
user.setPassword(encryptedPassword);
// 保存用户
return userMapper.insert(user) > 0;
}
@Override
public User login(String username, String password) {
// 查询用户
User user = userMapper.findByUsername(username);
if (user != null) {
// 这里使用简单的MD5加密示例
String encryptedPassword = MD5Util.encrypt(password);
// 和数据库中的密码进行比较
if (user.getPassword().equals(encryptedPassword)) {
return user;
}
}
return null;
}
}20.6 Controller层
创建登录Controller ,这里将登录和注册都放在一个 Controller 中了。
LoginController.java:
java
package com.foooor.helloweb.controller;
import com.foooor.helloweb.pojo.User;
import com.foooor.helloweb.service.IUserService;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
@RequestMapping("/views")
public class LoginController {
@Autowired
private IUserService userService;
/**
* 跳转到注册页面
*/
@GetMapping("/to-register")
public String toRegister() {
return "register";
}
/**
* 跳转到登录页面
*/
@GetMapping("/to-login")
public String toLogin() {
return "login";
}
/**
* 处理登录请求
*/
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpSession session, Model model) {
// 调用Service层的登录方法进行验证
User user = userService.login(username, password);
if (user != null) {
// 将用户信息存入Session
session.setAttribute("loginUser", user);
// 登录成功,重定向到学生列表页面
return "redirect:/views/students";
} else {
// 登录失败,返回登录页面并显示错误信息
model.addAttribute("error", "用户名或密码错误");
return "login";
}
}
/**
* 处理注册请求
*/
@PostMapping("/register")
public String register(@RequestParam String username,
@RequestParam String password,
@RequestParam String confirmPassword,
Model model) {
// 验证密码是否一致
if (!password.equals(confirmPassword)) {
model.addAttribute("error", "两次输入的密码不一致");
return "register";
}
// 创建用户对象
User user = new User();
user.setUsername(username);
user.setPassword(password);
// 注册用户
boolean success = userService.register(user);
if (success) {
// 注册成功,重定向到登录页面
return "redirect:/views/to-login";
} else {
// 注册失败,返回注册页面并显示错误信息
model.addAttribute("error", "用户名已存在");
return "register";
}
}
/**
* 处理退出登录
*/
@GetMapping("/logout")
public String logout(HttpSession session) {
// 销毁Session
session.invalidate();
// 重定向到登录页面
return "redirect:/views/to-login";
}
}- 在其中分别处理登录、注册、退出功能;
- 注册后重定向到登录页面;
- 登录后重定向到学生列表页面。
20.7 Thymeleaf模板
下面再创建两个页面,一个是注册页面,一个是登录页面。还是在模板目(webapp/WEB-INF/template/)下创建。
创建注册页面模板 register.html:
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>注册</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 400px;
margin: 100px auto;
padding: 20px;
border: 1px solid #ccc;
border-radius: 5px;
}
h1 {
text-align: center;
}
.error {
color: red;
text-align: center;
margin-bottom: 20px;
}
div {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
}
input[type="text"], input[type="password"] {
width: 100%;
padding: 8px;
box-sizing: border-box;
}
input[type="submit"] {
width: 100%;
padding: 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
input[type="submit"]:hover {
background-color: #45a049;
}
.login-link {
text-align: center;
margin-top: 15px;
}
</style>
</head>
<body>
<h1>用户注册</h1>
<!-- 显示错误信息 -->
<p class="error" th:if="${error}">[[${error}]]</p>
<form th:action="@{/views/register}" method="post">
<div>
<label>用户名:</label>
<input type="text" name="username" required>
</div>
<div>
<label>密码:</label>
<input type="password" name="password" required>
</div>
<div>
<label>确认密码:</label>
<input type="password" name="confirmPassword" required>
</div>
<div>
<input type="submit" value="注册">
</div>
</form>
<div class="login-link">
<span>已有账号?</span>
<a th:href="@{/views/to-login}">立即登录</a>
</div>
</body>
</html>页面显示效果如下:

创建登录页面模板 login.html:
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 400px;
margin: 100px auto;
padding: 20px;
border: 1px solid #ccc;
border-radius: 5px;
}
h1 {
text-align: center;
}
.error {
color: red;
text-align: center;
margin-bottom: 20px;
}
div {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
}
input[type="text"], input[type="password"] {
width: 100%;
padding: 8px;
box-sizing: border-box;
}
input[type="submit"] {
width: 100%;
padding: 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
input[type="submit"]:hover {
background-color: #45a049;
}
.register-link {
text-align: center;
margin-top: 15px;
}
</style>
</head>
<body>
<h1>用户登录</h1>
<!-- 显示错误信息 -->
<p class="error" th:if="${error}">[[${error}]]</p>
<form th:action="@{/views/login}" method="post">
<div>
<label>用户名:</label>
<input type="text" name="username" required>
</div>
<div>
<label>密码:</label>
<input type="password" name="password" required>
</div>
<div>
<input type="submit" value="登录">
</div>
</form>
<div class="register-link">
<span>还没有账号?</span>
<a th:href="@{/views/to-register}">立即注册</a>
</div>
</body>
</html>显示效果如下:

20.8 登录拦截器
项目到这里,是已经可以注册用户和实现登录了。
但是现在还没有实现对学生列表进行访问控制,也就是不管登没登录,通过 http://localhost:8080/view/students 都可以访问学生列表页面。下面就来实现访问拦截功能。
创建登录拦截器 LoginInterceptor.java:
java
package com.foooor.helloweb.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取Session
HttpSession session = request.getSession();
// 检查Session中是否有登录用户信息
Object user = session.getAttribute("loginUser");
if (user == null) {
// 如果没有登录,重定向到登录页面
response.sendRedirect("/views/to-login");
return false;
}
// 如果已经登录,继续处理请求
return true;
}
}- 在拦截器中,从 session 中取出登录信息,也就是在登录的时候放进去的,来判断用户有没有登录,如果登录了,则放行;如果没有登录,就跳转到登录页面。
20.9 拦截器配置
在 SpringMvcConfig 类中配置拦截器,并设置例外,需要注意,不要拦截登录和注册相关的接口,否则登录功能和注册功能会失效。
还可能造成循环,没登录跳转到登录,但是因为拦截了登录,导致又跳转到登录,死循环了。
在 SpringMvcConfig 配置类中注入并配置拦截器:
java
@Configuration
@ComponentScan(basePackages = "com.foooor.helloweb.controller")
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
// 注入登录拦截器
@Autowired
private LoginInterceptor loginInterceptor;
/**
* 配置拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 其他拦截器配置,略...
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 拦截所有请求
// 添加例外,不拦截登录和注册页面
.excludePathPatterns(
"/views/to-login",
"/views/login",
"/views/to-register",
"/views/register"
);
}
// 其他,略...
}20.10 测试
现在项目基本完成,可以进行测试了。
可以进行用户的注册和登录。
登录成功后,会跳转到学生列表页面。
如果没登录,直接访问学生列表会跳转到登录页面。如果登录了就没问题了。
你可以在学生列表添加一个超链接,用于用户退出:
html
<a th:href="@{/views/logout}">退出</a>现在整个功能是基本完成了,只是比较粗糙,很多细节需要完善,例如用户名和密码的校验规则等等,这里只是演示一下实现的流程。
内容未完......