HarryTruman

 

用户登录和权限校验

在大多数系统中都是必要的,一万年也要学会,这里使用 Sa-Token v1.45.0 实现登录和校验,非常容易上手。

Sa-Token官网( ̄︶ ̄)↗

写入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 请求设计

  • 可以访问 HttpServletRequestHttpServletResponse,直接获取请求头中的 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)    收藏  举报

导航