用户登录和权限校验
在大多数系统中都是必要的,一万年也要学会,这里使用 Sa-Token v1.45.0 实现登录和校验,非常容易上手。
写入pom文件:这里引入的BCrypt工具类用来做用户密码加密和校验,不依赖框架,纯工具。
<!--sa-token-->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot4-starter</artifactId>
<version>1.45.0</version>
</dependency>
<!--纯原生 BCrypt 工具类-->
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>
5.1 用户登录
流程:
1.输入用户名和密码 → 2.传入后端验证 → 3.用户名/密码不为空 → 4.用户名和密码比对 ─T→ 5.返回用户id、用户名和token
↓F
6.报错“用户名或密码错误”
1、第一步当然是想着接口和DTO/VO对象怎么设计,这里呢仿照了《苍穹外卖》:
接口地址:/user/login
请求方式:POST
请求示例:
{
"name": "钟山",
"password": 123456
}
响应示例:
{
"code": 0,
"message": "",
"data": {
"id": 1,
"name": "zhongshan",
"password": 123456,
"token": ""
},
"timestamp": 0
}
定义了用户登录DTO,对应请求
@Data
@Schema(description = "用户登录传入对象")
public class UserLoginDTO {
@Schema(description = "用户名", example = "秦十十")
private String name;
@Schema(description = "密码", example = "123456")
private String password;
}
定义了用户登录VO,对应响应
F:完了在这一看,密码都给返回前端了,那用户害怎么保障安全呐?
Q:在数据库的存储中,只存储加密后不可逆的字段,前端请求体中的密码只与该字段进行校验,不存储。
@Data
@Builder
@Schema(description = "用户登录传出对象")
public class UserLoginVO {
@Schema(description = "主键值", example = "1")
private Long id;
@Schema(description = "用户名", example = "秦十十")
private String name;
@Schema(description = "密码", example = "123456")
private String password;
@Schema(description = "token")
private String token;
}
2、跳过controller,直接来看UserServiceImpl怎么写
/**
* 用户登录
*
* @param userLoginDTO 用户登录dto
* @return {@link UserLoginVO }
*/
@Override
public UserLoginVO login(UserLoginDTO userLoginDTO) {
String name = userLoginDTO.getName();
String password = userLoginDTO.getPassword();
// 是否为空
if (name == null || name.isEmpty() || password == null || password.isEmpty()) {
throw new LoginException(MessageConstant.NAME_OR_PASSWORD_IS_NULL);
}
// 不为空 - 获取
User user = userMapper.findByName(name);
if (user == null) {
throw new SelectNotFoundException(MessageConstant.NAME_OR_PASSWORD_IS_INCORRECT);
}
// 校验密码
if (!PasswordUtil.verify(password, user.getPassword())) {
throw new LoginException(MessageConstant.NAME_OR_PASSWORD_IS_INCORRECT);
}
// 通过 - 登录(Sa-Token 会自动将用户ID与token关联,后续请求可通过 StpUtil.getLoginId() 获取)
StpUtil.login(user.getId());
return UserLoginVO.builder()
.id(user.getId())
.name(user.getName())
.password(user.getPassword())
.token(StpUtil.getTokenValue())
.build();
}
3、校验密码由PasswordUtil工具类实现
BCrypt - 安全的象征
/**
* 密码加密工具类(BCrypt算法,企业级安全标准)
* 自动加盐、不可逆、防暴力破解/彩虹表攻击
*/
public class PasswordUtil {
private PasswordUtil() {}
/**
* 明文密码加密(注册/修改密码时使用)
* @param rawPassword 用户输入的明文密码
* @return 加密后的60位字符串(存入数据库)
*/
public static String encode(String rawPassword) {
if (rawPassword == null || rawPassword.isEmpty()) {
throw new IllegalArgumentException("密码不能为空");
}
return BCrypt.hashpw(rawPassword, BCrypt.gensalt());
}
/**
* 密码验证(登录时使用)
* @param rawPassword 用户输入的明文密码
* @param encodedPassword 数据库中存储的加密密码
* @return true=密码正确,false=密码错误
*/
public static boolean verify(String rawPassword, String encodedPassword) {
if (rawPassword == null || encodedPassword == null) {
return false;
}
return BCrypt.checkpw(rawPassword, encodedPassword);
}
}
4、登录成功后,sa-token会根据传入的唯一id,自动生成token并且返回前端
我这里又手动返回了一个token纯粹是上个项目惯性
// 关于用户登录和校验,token等内容sa-token已经后台搞定了,代码只需要写这几个:
StpUtil.login(user.getId()); // 登录(参数是唯一标识)
StpUtil.checkLogin(); // 校验
StpUtil.getLoginId() // 获取login()中传入的id
SaHolder.getRequest().getRequestPath() // 获取请求url地址,方便调试
5.2 权限校验
用户登录完了,获得了token这个通行证,默认被保存在了cookie中,后续用户发起的每一个请求都会默认携带这个token。于是乎,后端只需要统一拦截这些请求,每次都校验是不是合法用户就行了。
🙋:又是统一又是拦截,用面向切面编程行不行?
deepseek说:不行:
拦截器天然为 Web 请求设计
- 可以访问
HttpServletRequest和HttpServletResponse,直接获取请求头中的 Token,也能直接返回错误响应(如 401)。 - 可以控制是否继续执行 Controller(
preHandle返回false则中断)。 - 支持对所有或指定 URL 模式进行拦截,配置灵活。
AOP 不适合这个场景
- AOP 切面(如
@Around)虽然可以拦截 Controller 方法,但难以直接获取和修改 HttpServletResponse(需要额外参数传递),代码不直观。 - AOP 主要用于业务层或数据层的横切关注点(日志、事务、缓存、性能监控等),而不是 Web 层的请求过滤。
- 用 AOP 做 Token 验证往往要借助
RequestContextHolder获取当前请求,绕了一圈,增加了复杂度。
…………
好的,开始学习拦截器:
位置:
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.itstudent.springbootdemo/
│ │ │ ├── config # 配置
│ │ │ │ └── WebMvcConfig.java # WebMvc配置类
│ │ │ │
│ │ │ ├── interceptor # 拦截器
│ │ │ │ └── SaTokenInterceptor.java # SaToken拦截器
对拦截器的操作有两步骤:1、定义拦截器;2、配置类中注册拦截器
符合职责分离原则,易维护、易扩展。
程序:
SaTokenInterceptor.java 定义拦截器
@Component
@Slf4j
// 定义拦截器
public class SaTokenInterceptor implements HandlerInterceptor {
@Override
// 重写方法preHandle:调用controller方法前执行如下逻辑
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
try {
log.info("前端访问path:{}", SaHolder.getRequest().getRequestPath());
StpUtil.checkLogin(); // 会检验token,如果不合法则抛出异常
log.info("此path校验成功,当前用户id:{}", StpUtil.getLoginId());
return true;
} catch (Exception e) {
log.error("此 path 校验失败:{}", SaHolder.getRequest().getRequestPath());
throw e;
}
}
}
WebMvcConfig.java 配置类 - 注册拦截器
@Configuration
@Slf4j
// 使用implements保留springboot默认配置
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private SaTokenInterceptor saTokenInterceptor;
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册sa-token拦截器,校验规则为checkLogin()
registry.addInterceptor(saTokenInterceptor) // 注册拦截器
.addPathPatterns("/**") // 定义需要拦截的路径:所有
.excludePathPatterns( // 排除一些路径
"/user/login",
"/swagger-ui/**", // knife4j请求资源路径排除,没有token
"/favicon.ico",
"/webjars/**",
"/v3/api-docs/**",
"/doc.html",
"/error" // 必须排除:防止404转发导致上下文异常
);
}
}
posted on 2026-06-10 21:49 HarryTruman 阅读(6) 评论(0) 收藏 举报
浙公网安备 33010602011771号