当前位置: 首页 > article >正文

Spring Boot + Redis + Sa-Token

参考文献

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 {

    @Override
    public String getLoginId() {
        // 返回当前登录用户的ID
        return "123"; // 假设返回用户ID为123
    }

    @Override
    public boolean isLogin() {
        // 判断当前用户是否登录
        return true; // 假设用户已登录
    }

    @Override
    public void login(Object loginId) {
        // 实现用户登录逻辑
        // 这里可以根据传入的loginId来设置用户的登录状态
    }

    @Override
    public void logout() {
        // 实现登出逻辑
        // 清除用户的登录状态
    }

    @Override
    public boolean hasPermission(String permission) {
        // 判断用户是否有某个权限
        return "admin".equals(permission); // 假设只有管理员有权限
    }

    @Override
    public 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 -> USER

INSERT 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/doLogin?username=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.kler.cn/a/465529.html

相关文章:

  • 电脑里msvcr120.dll文件丢失怎样修复?
  • vue3 vite 动态加载路由遇到的问题
  • Leetcode打卡:我的日程安排表III
  • ARM 汇编基础总结
  • 深入Android架构(从线程到AIDL)_10 主线程(UI 线程)的角色
  • Docker安装Prometheus和Grafana
  • 学英语学压测:01开源压测工具jmeter能干什么
  • GO:复用对象和协程资源
  • GPT与MBR之间的差异是什么?
  • Node Exporter常用Prometheus指标
  • Tailwind CSS 实战:性能优化最佳实践
  • node.js卸载并重新安装(超详细图文步骤)
  • 如何让ElasticSearch完美实现数据库的Like查询
  • 改投论文时如何重构
  • uniapp安卓命名坑
  • uniapp配置文字艺术字体风格
  • Linux(Ubuntu)下ESP-IDF下载与安装完整流程(2)
  • 动态规划<八> 完全背包问题及其余背包问题
  • 十二、Vue 路由
  • windows征服nginx(1)
  • MYSQL在Windows平台上的限制
  • Docker安装Prometheus和Grafana
  • 国产固态继电器如何满足物联网应用的需求
  • html+css网页制作 美食 美食网5个页面
  • 如何轻松安全地销售旧 Android 手机
  • C++并发编程之内存屏障