Spring Boot + Redis + Sa-Token

news/2025/3/1 15:34:10/

参考文献

Sa-Token实现分布式登录鉴权(Redis集成 前后端分离)-腾讯云开发者社区-腾讯云

介绍

StpInterface 是 Sa-Token 框架中的一个接口,属于 Sa-Token 身份认证与授权框架的一部分。该接口提供了一些方法来实现自定义的身份认证和授权管理功能,特别是针对自定义的权限验证。

StpInterface类的主要功能

StpInterface 用于定义 Sa-Token 中与用户身份相关的核心操作接口。通过实现这个接口,用户可以自定义如何获取用户信息、验证用户身份、判断是否有权限等。

主要方法

StpInterface 主要包括以下几个常用的方法:

  1. getLoginId():获取当前登录用户的唯一标识(例如用户 ID)。

    String getLoginId();

  2. isLogin():判断当前是否已登录。

    boolean isLogin();

  3. login(Object loginId):登录方法,传入一个唯一标识来进行用户登录。

    void login(Object loginId);

  4. logout():登出方法,清除用户的登录状态。

    void logout();

  5. hasPermission(String permission):判断当前登录用户是否具有某个权限。

    boolean hasPermission(String permission);

  6. hasRole(String role):判断当前登录用户是否拥有某个角色。

    boolean hasRole(String role);

使用场景
  • 自定义身份认证:如果需要自定义登录逻辑或用户身份验证,可以实现 StpInterface 接口来替代 Sa-Token 默认的用户认证方式。
  • 角色与权限管理:通过 hasRolehasPermission 等方法,进行角色与权限的验证,保证应用中的授权机制符合业务需求。
示例

以下是一个简单的实现例子,展示了如何实现 StpInterface 接口来定制认证与授权逻辑:

import cn.dev33.satoken.stp.StpInterface;
import org.springframework.stereotype.Component;@Component
public class MyStpInterface implements StpInterface {@Overridepublic String getLoginId() {// 返回当前登录用户的IDreturn "123"; // 假设返回用户ID为123}@Overridepublic boolean isLogin() {// 判断当前用户是否登录return true; // 假设用户已登录}@Overridepublic void login(Object loginId) {// 实现用户登录逻辑// 这里可以根据传入的loginId来设置用户的登录状态}@Overridepublic void logout() {// 实现登出逻辑// 清除用户的登录状态}@Overridepublic boolean hasPermission(String permission) {// 判断用户是否有某个权限return "admin".equals(permission); // 假设只有管理员有权限}@Overridepublic boolean hasRole(String role) {// 判断用户是否有某个角色return "admin".equals(role); // 假设只有管理员有该角色}
}

通过实现 StpInterface,你可以根据实际业务需求来定制用户认证、登录状态、权限验证等操作。

基于原有的基础 | 创建MySQL表单存储权限

-- 角色表
CREATE TABLE roles (id BIGINT AUTO_INCREMENT PRIMARY KEY,role_name VARCHAR(50) NOT NULL
);-- 权限表
CREATE TABLE permissions (id BIGINT AUTO_INCREMENT PRIMARY KEY,permission_name VARCHAR(50) NOT NULL
);-- 用户角色关联表
CREATE TABLE users_roles (user_id BIGINT NOT NULL,role_id BIGINT NOT NULL,FOREIGN KEY (user_id) REFERENCES users(id),FOREIGN KEY (role_id) REFERENCES roles(id)
);-- 角色权限关联表
CREATE TABLE roles_permissions (role_id BIGINT NOT NULL,permission_id BIGINT NOT NULL,FOREIGN KEY (role_id) REFERENCES roles(id),FOREIGN KEY (permission_id) REFERENCES permissions(id)
);-- 用户表
CREATE TABLE users (id BIGINT AUTO_INCREMENT PRIMARY KEY,        -- 用户唯一标识(主键)username VARCHAR(50) NOT NULL UNIQUE,       -- 用户名(唯一)password VARCHAR(255) NOT NULL,             -- 密码(加密存储)email VARCHAR(100) DEFAULT NULL,            -- 邮箱(可选)phone VARCHAR(20) DEFAULT NULL,             -- 手机号(可选)status TINYINT DEFAULT 1,                   -- 用户状态(1=启用, 0=禁用)create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间last_login_time TIMESTAMP NULL DEFAULT NULL -- 上次登录时间
);INSERT INTO users (username, password, email, phone, status, create_time, last_login_time) VALUES
('zhangsan', '$2a$10$eC9yWZaMjMEbfBOAAsXHg.SUz3aHtYZJ/riMjHJ.TOu3NHsMFTm.a', 'zhangsan@example.com', '1234567890', 1, NOW(), NULL),
('lisi', '$2a$10$txB4zY7lqr9Kx.XHcGB5ruMiOBpFMHLF9rljN5iGtZ1o26g/.Agxe', 'lisi@example.com', '0987654321', 1, NOW(), NULL);INSERT INTO roles (id, role_name) VALUES
(1, 'ADMIN'),
(2, 'USER');INSERT INTO permissions (id, permission_name) VALUES
(1, 'user.add'),
(2, 'user.delete'),
(3, 'user.update'),
(4, 'user.view');INSERT INTO users_roles (user_id, role_id) VALUES
(1, 1), -- zhangsan -> ADMIN
(2, 2); -- lisi -> USERINSERT INTO roles_permissions (role_id, permission_id) VALUES
(1, 1), -- ADMIN 拥有 user.add 权限
(1, 2), -- ADMIN 拥有 user.delete 权限
(1, 3), -- ADMIN 拥有 user.update 权限
(1, 4), -- ADMIN 拥有 user.view 权限
(2, 4); -- USER 只拥有 user.view 权限

注意:密码已经使用 bcrypt 加密,明文分别为:

  • zhangsan: password123
  • lisi: mypassword

UserController

package com.example.satokendemo.controller;  import cn.dev33.satoken.stp.SaTokenInfo;  
import cn.dev33.satoken.stp.StpUtil;  
import cn.dev33.satoken.util.SaResult;  
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;  
import com.example.satokendemo.mapper.UserMapper;  
import com.example.satokendemo.mapper.PermissionMapper;  
import com.example.satokendemo.pojo.User;  
import com.example.satokendemo.util.PasswordUtil;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  import java.util.List;  @RestController  
@RequestMapping("/user")  
public class UserController {  @Autowired  private UserMapper userMapper;  @Autowired  private PermissionMapper permissionMapper;  /**  * 用户登录  */  @RequestMapping("/doLogin")  public SaResult doLogin(String username, String password) {  // 第1步:从数据库查询用户信息  QueryWrapper<User> queryWrapper = new QueryWrapper<>();  queryWrapper.eq("username", username);  User user = userMapper.selectOne(queryWrapper);  // 查询用户  if (user == null) {  System.out.println("用户不存在");  } else {  System.out.println("找到用户: " + user.getUsername());  }  if (!PasswordUtil.verify(password, user.getPassword())) {  System.out.println("用户输入密码: " + password);  System.out.println("数据库存储的加密密码: " + user.getPassword());  System.out.println("密码校验失败");  }  // 如果用户不存在或者密码不匹配,返回登录失败  if (user == null || !PasswordUtil.verify(password, user.getPassword())) { // 使用加密策略校验密码  return SaResult.error("用户名或密码错误");  }  // 第2步:登录  StpUtil.login(user.getId());  // 第3步:加载用户信息和权限信息  StpUtil.getSession().set("loginInfo", user);  // 加载用户权限  List<String> authList = permissionMapper.getPermissionsByUserId(user.getId());  StpUtil.getSession().set("authList", authList);  // 第4步:获取 Token 相关参数  SaTokenInfo tokenInfo = StpUtil.getTokenInfo();  // 第5步:返回给前端  return SaResult.data(tokenInfo);  }  /**  * 查询登录状态  */  @RequestMapping("/isLogin")  public String isLogin() {  return "当前会话是否登录:" + StpUtil.isLogin();  }  /**  * 获取当前登录用户信息  */  @RequestMapping("/getUserInfo")  public User getUserInfo() {  return (User) StpUtil.getSession().get("loginInfo");  }  /**  * 测试方法:校验权限 - 添加操作  */  @GetMapping("/add")  public String add() {  StpUtil.checkPermission("user.add");  return "ok";  }  /**  * 测试方法:校验权限 - 更新操作  */  @GetMapping("/update")  public String update() {  StpUtil.checkPermission("user.update");  return "ok";  }  
}

这里我把controller中的模拟数据改成了mysql数据库的数据并将密码进行加密,注册时也可以通过相应的方法对明文密码进行加密处理后存储至数据库中,使得数据更加安全。

完成后的图例应该如下图所示:

数据库的用户表单(角色,权限,具体信息,绑定关系…)
在这里插入图片描述

在原本代码的基础上和MySQL连接并完成权限认证与登录

其它的表单的实体类都可以直接使用mybatis-plus去完成增删改查,但permissions表单的实体类得在mapper中添加一个方法使用。

PermissionMapper
完成对权限的使用

public interface PermissionMapper extends BaseMapper<Permission> {  // 获取用户的所有权限名称  @Select("SELECT p.permission_name " +  "FROM permissions p " +  "JOIN roles_permissions rp ON p.id = rp.permission_id " +  "JOIN users_roles ur ON rp.role_id = ur.role_id " +  "WHERE ur.user_id = #{userId}")  List<String> getPermissionsByUserId(@Param("userId") Long userId);  
}

之后是我yml的配置

server:  # 端口  port: 8081  spring:  datasource:  username: root  password: 20050101  url: jdbc:mysql://localhost:3306/sa_token?serverTimezone=UTC&userUnicode=true&characterEncoding=utf-8  driver-class-name: com.mysql.cj.jdbc.Driver  # redis配置  redis:  # Redis数据库索引(默认为0)  database: 0  # Redis服务器地址  host: 127.0.0.1  # Redis服务器连接端口  port: 6379  # Redis服务器连接密码(默认为空)  # password:  # 连接超时时间  timeout: 10s  lettuce:  pool:  # 连接池最大连接数  max-active: 200  # 连接池最大阻塞等待时间(使用负值表示没有限制)  max-wait: -1ms  # 连接池中的最大空闲连接  max-idle: 10  # 连接池中的最小空闲连接  min-idle: 0  jpa:  open-in-view: false  mybatis:  mapper-locations: classpath:mapper/*.xml  type-aliases-package: com.example.satokendemo.pojo  
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############  
sa-token:  # token名称 (同时也是cookie名称)  token-name: satoken  # token有效期,单位s 默认30天, -1代表永不过期  timeout: 2592000  # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒  activity-timeout: -1  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)  is-concurrent: true  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)  is-share: true  # token风格  token-style: uuid  # 是否输出操作日志  is-log: false

在这里插入图片描述

这是spring boot项目的大体框架,可以按照我这个来。

之后启动项目
通过api工具postman进行登录测试,例如:http://localhost:8082/user/doLoginusername=lisi&password=mypassword

/**  * 示例代码:生成加密密码(可用于初始化数据库)  */  
public static void main(String[] args) {  String userPassword = "password123"; // 明文密码  String storedPassword = "$2a$10$eC9yWZaMjMEbfBOAAsXHg.SUz3aHtYZJ/riMjHJ.TOu3NHsMFTm.a"; // 从数据库获取的加密密码  boolean isValid = PasswordUtil.verify(userPassword, storedPassword);  System.out.println("密码验证结果: " + isValid);  String encryptedPassword = PasswordUtil.encrypt(userPassword);  System.out.println("加密后的密码: " + encryptedPassword);  System.out.println("加密后的密码与数据库中的密码是否匹配: " + PasswordUtil.verify(userPassword, encryptedPassword  )); // 再次验证加密后的密码  
}

记得用这个工具类去改一下数据库的加密密码

使用postman测试后可以得到以下信息

在这里插入图片描述

redis中也可以看到我们的数据已经传递成功了
在这里插入图片描述

无论是权限还是相关的用户信息都是已经成功传到redis缓存了,之后就是携带token去测试接口调用看是否符合我们的权限。

不携带token去访问接口 http://localhost:8081/user/add

在这里插入图片描述

携带token访问

在这里插入图片描述

没有相应的权限的用户携带token去访问

在这里插入图片描述


http://www.ppmy.cn/news/1575806.html

相关文章

Web自动化之Selenium添加网站Cookies实现免登录

在使用Selenium进行Web自动化时&#xff0c;添加网站Cookies是实现免登录的一种高效方法。通过模拟浏览器行为&#xff0c;我们可以将已登录状态的Cookies存储起来&#xff0c;并在下次自动化测试或爬虫任务中直接加载这些Cookies&#xff0c;从而跳过登录步骤。 Cookies简介 …

信息系统的安全防护

文章目录 引言**1. 物理安全****2. 网络安全****3. 数据安全****4. 身份认证与访问控制****5. 应用安全****6. 日志与监控****7. 人员与管理制度****8. 其他安全措施****9. 安全防护框架**引言 从技术、管理和人员三个方面综合考虑,构建多层次、多维度的安全防护体系。 信息…

搭建elasticsearch集群,8.17.0版本

搭建集群的帖子有很多&#xff0c;但是似乎新版做了一些改动。 下面说一下我启动的步骤&#xff1a;先用如下命令启动第一个节点 docker run --name es01 -it --privileged --memory"2g" \-p 9200:9200 -p 9300:9300 \-e cluster.namemy-application \-e node.name…

Virtual Scrolling 虚拟滚动优化方案

虚拟滚动&#xff08;Virtual Scrolling&#xff09;是一种优化前端渲染大量数据的技术&#xff0c;它通过按需渲染可见区域的内容&#xff0c;避免一次性创建所有 DOM 元素&#xff0c;从而解决性能问题。以下是其核心原理&#xff1a; 1. 核心思想 物理世界&#xff1a;假设…

Rust学习总结之-match

Rust 有一个叫做 match 的极为强大的控制流运算符&#xff0c;它允许我们将一个值与一系列的模式相比较&#xff0c;并根据相匹配的模式执行相应代码。模式可由字面量、变量、通配符和许多其他内容构成。 一&#xff1a;match定义 可以把 match 表达式想象成某种硬币分类器&a…

YOLOv11-ultralytics-8.3.67部分代码阅读笔记-ops.py

ops.py ultralytics\models\utils\ops.py 目录 ops.py 1.所需的库和模块 2.class HungarianMatcher(nn.Module): 3.def get_cdn_group(batch, num_classes, num_queries, class_embed, num_dn100, cls_noise_ratio0.5, box_noise_scale1.0, trainingFalse): 1.所需的库…

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_init_cycle 函数 - 详解(9)

详解&#xff08;9&#xff09; 获取并存储主机名 if (gethostname(hostname, NGX_MAXHOSTNAMELEN) -1) {ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "gethostname() failed");ngx_destroy_pool(pool);return NULL;}/* on Linux gethostname() silently truncat…

软件工程复试专业课-软件生命周期

文章目录 软件过程模型瀑布模型模型图特点优缺点改进后的瀑布模型 快速原型模型模型图优缺点 增量模型&#xff08;迭代-递增模型&#xff09;原型图与瀑布和快速原型的区别优缺点风险更大的增量模型 螺旋模型简介模型图优缺点 喷泉模型模型图优缺点 编码修补模型敏捷过程优缺点…