SaToken+SpringBoot+Redis前后端分离登录认证

目录

  • 前言
  • 一、创建工程项目🎍
    • 1.1 创建后端工程
    • 1.2 创建前端工程
  • 二、业务代码🎊
    • 后端代码
    • 前端代码
  • 三、测试
  • 参考资料

前言

Sa-Token 是一款 Java 语言的权限认证框架,提供了灵活、高效、易用的权限认证和会话管理功能。它是 SpringBoot、Spring MVC、Servlet 等 Java 技术体系下的轻量级权限认证组件,可以帮助开发者快速实现用户认证、授权和会话管理等功能。

logo

Sa-Token官方文档

功能结构图

img

认证流程图

sa-token-rz

框架特性

image-20240523135615846

一、创建工程项目🎍

1.1 创建后端工程

引入依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.13</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.38.0</version>
</dependency>

<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-redis-fastjson2</artifactId>
    <version>1.37.0</version>
</dependency>
<!--无论使用哪种序列化方式,你都必须为项目提供一个 Redis 实例化方案-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

注:如果你使用的是 SpringBoot 3.x,只需要将 sa-token-spring-boot-starter 修改为 sa-token-spring-boot3-starter 即可。

基础配置

server:
  port: 8081
spring:
  redis:
    database: 1
    host: 127.0.0.1
    port: 6379
    password:
    timeout: 5000
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 0
#Sa-token相关配置(与官网一致)
sa-token: 
    # token 名称(同时也是 cookie 名称)
    token-name: satoken
    # token 有效期(单位:秒) 默认30天,-1 代表永久有效
    timeout: 2592000
    # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
    active-timeout: -1
    # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
    is-concurrent: true
    # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
    is-share: true
    # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
    token-style: uuid
    # 是否输出操作日志 
    is-log: true

1.2 创建前端工程

本项目使用的vue3,创建一个vue项目,引入下方依赖即可

"dependencies": {
  "axios": "^1.6.8",
  "element-plus": "^2.7.3",
  "pinia": "^2.1.7",
  "qs": "^6.12.1",
  "vue": "^3.2.37",
  "vue-router": "^4.2.5"
},

二、业务代码🎊

后端代码

User.java

@Data
public class User{
    //id
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    //用户名
    private String username;
    //密码
    private String password;
    //账户是否锁住(1被锁0未被锁)
    private Integer isLocked;
    //账户是否被删除(1删除0未被删除)
    @TableLogic
    private Integer isDelete;
    //创建时间
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    //更新时间
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

UserDTO.java

@EqualsAndHashCode(callSuper = true)
@Data
public class UserDTO extends User {
    //记住我
    private boolean rememberMe;
    //角色列表
    private List<Role> roleList;
    //登录的设备
    private String device;
    //图形验证码
    private String code;
    //图形验证码的key
    private String codeKey;

}

UserController.java

注意:代码中定义了一些常量,可以自行替代。

@RestController
@RequestMapping("/user")
@CrossOrigin
public class UserController {
    
    @Autowired
    UserService userService;
    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @PostMapping("/doLogin")
    public SaResult doLogin(@RequestBody UserDto userDto) {
        //从redis中取生成的验证码
        String validateCode = stringRedisTemplate.opsForValue().get("code:validate:" + userDto.getCodeKey());
        if (userDto.getCode().equals(validateCode)) {
            //验证码正确才进行用户验证,先验证了用户存不存在,再验证了密码是否正确
            if (userService.login(userDto.getUsername(), userDto.getPassword())) {
                StpUtil.login(userDto.getUsername(), new SaLoginModel()
                         //实现‘记住我’功能
                        .setIsLastingCookie(userDto.isRememberMe())
                         //设置登录设备,主要用于实现同端互斥登录,此处没有实现该功能,可以不用管
                        .setDevice("PC"));
                //验证成功就以json的形式返回token
                HashMap<String, Object> resultMap = new HashMap<>();
                resultMap.put("token", StpUtil.getTokenValue());
                return SaResult
                        .ok(ResponseCode.LOGIN_SUCCESS.getMessage())
                        .setCode(ResponseCode.LOGIN_SUCCESS.getCode())
                        .setData(resultMap);
            } else {
                return SaResult
                        .error(ResponseCode.USERNAME_PASSWORD_ERROR.getMessage())
                        .setCode(ResponseCode.USERNAME_PASSWORD_ERROR.getCode());
            }
        } else {
            return SaResult
                    .error(ResponseCode.VALIDATE_CODE_ERROR.getMessage())
                    .setCode(ResponseCode.VALIDATE_CODE_ERROR.getCode());
        }

    }

}

StpUtils.login:

  1. 检查此账号是否之前已有登录;
  2. 为账号生成 Token 凭证与 Session 会话;
  3. 记录 Token 活跃时间;
  4. 通知全局侦听器,xx 账号登录成功;
  5. Token 注入到请求上下文;
  6. 等等其它工作……

SaResult:这个也是一个由Satoken封装的结果响应类,还是挺好用的。

UserService.java

public interface UserService {
    boolean login(String username, String password);
}

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    UserDao userDao;
    
        @Override
    public boolean login(String username, String password) {
        User user = userDao.selectOne(new QueryWrapper<User>().eq("username", username));
        //数据库中的密码进行了加密BCrypt也是Satoken提供的一个工具类
        return user != null && BCrypt.checkpw(password, user.getPassword());
    }
}

UserDao.java

/**
 * (User)表数据库访问层
 *
 * @author yzk
 * @since 2024-05-15 16:44:29
 */
public interface UserDao extends BaseMapper<User> {

}

SaAuthenticationConfigure.java

@Configuration
public class SaAuthenticationConfigure implements WebMvcConfigurer {

    // 注册 Sa-Token 拦截器,打开注解式鉴权功能 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器,打开注解式鉴权功能
        registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
     			//所有接口都会检查是否登录了
                .addPathPatterns("/**")
            	//以下接口不检查,直接放行
                .excludePathPatterns("/user/doLogin")
    }

}

SaTokenFilter.java

@Configuration
public class SaTokenFilter implements WebMvcConfigurer {

    /**
     * 注册 [Sa-Token 全局过滤器] 
     */
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()
               
               // 指定 [拦截路由] 与 [放行路由]
               .addInclude("/**").addExclude("/favicon.ico")
               
               // 认证函数: 每次请求执行 
               .setAuth(obj -> {
                SaManager.getLog().debug("----- 请求path={}  提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
                  // ...
               })
               
               // 异常处理函数:每次认证函数发生异常时执行此函数 
               .setError(e -> {
                  return SaResult.error(e.getMessage());
               })
               
               // 前置函数:在每次认证函数之前执行
               .setBeforeAuth(obj -> {
                SaHolder.getResponse()

                  // ---------- 设置跨域响应头 ----------
                  // 允许指定域访问跨域资源
                  .setHeader("Access-Control-Allow-Origin", "*")
                  // 允许所有请求方式
                  .setHeader("Access-Control-Allow-Methods", "*")
                  // 允许的header参数
                  .setHeader("Access-Control-Allow-Headers", "*")
                  // 有效时间
                  .setHeader("Access-Control-Max-Age", "3600")
                  ;
                  
                  // 如果是预检请求,则立即返回到前端 
                  SaRouter.match(SaHttpMethod.OPTIONS)
                     .free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
                     .back();
               })
               ;
    }

}

注意这个过滤器!!!!!!!!!!务必配置这个过滤器,用SaToken时,后端使用@CrossOrigin进行了跨域的配置,但是!前端发起请求还是会报跨域问题,而且后端会报'未读取到有效token',是因为前端发起请求时,先发起了预检请求,SaToken拦截了预检请求,预检请求的headers中没有token,所以一直报错。

前端代码

此处删减了部分代码,只保留了登录登录相关的代码

login.vue

<template>
  <div class="login-container">
    <h2>登录</h2>
    <form @submit.prevent="handleSubmit">
      <div class="form-group">
        <label for="username">用户名:</label>
        <el-input type="text" id="username" v-model="user.username" placeholder="请输入用户名" required/>
      </div>
      <div class="form-group">
        <label for="password">密码:</label>
        <el-input type="password" id="password" v-model="user.password" placeholder="请输入密码" required/>
      </div>
      <div class="form-group">
        <label>验证码</label>
        <el-row :gutter="15">
          <el-col :span="14">
            <el-input v-model="user.code" style="margin-top: 7px" class="codeInput"
                      placeholder="请输入验证码"></el-input>
          </el-col>
          <el-col :span="10">
            <el-image @click="changeImage" style="width: 159px; height: 39px" :src="imageUrl"/>
          </el-col>
        </el-row>

      </div>
      <div class="form-group">
        <el-checkbox v-model="user.rememberMe" label="记住我" size="large"/>
      </div>

      <button type="submit">登录</button>
      <button type="submit" @click="toRegister">注册</button>

    </form>
    <div class="social-login">
      <button @click="toGitee">
        <span class="social-icon"></span>使用Gitee登录
      </button>
    </div>
  </div>
</template>

<script setup>
import {h, onMounted, ref} from 'vue';
import {generatorLoginCode, toGiteeOauth, userLogin} from "@/api/userApi";
import {useRouter} from "vue-router";
import {ElCheckbox, ElCol, ElImage, ElInput, ElNotification, ElRow} from "element-plus";

const router = useRouter()
const user = ref({
  username: "",
  password: "",
  rememberMe: false,
  code: "",
  codeKey: ""
})
const handleSubmit = () => {
  userLogin(JSON.stringify(user.value))
      .then((resp) => {
        if (resp.code === 2000) {
          //取出token并存入localStorage
          localStorage.setItem("token", resp.data.token)
          ElNotification({
            title: '提示',
            message: h('info', {style: 'color: teal'}, resp.msg),
            duration: 3000
          })
          router.push("/main")
        } else {
          if (resp.code === 5005) {
            ElNotification({
              title: '提示',
              message: h('error', {style: 'color: red'}, resp.msg),
              duration: 3000
            })
            changeImage()
          }
        }
      })
};
const userLogin = (data) => {
    return requests({
        url: "/user/doLogin",
        data: data,
        method: "POST",
    })
}
</script>

<style scoped>

</style>

前端点击登录按钮时,发后端接口发起请求,请求成功后,取出响应结果中的token存入localStorage。

requests.js

import axios from 'axios';
import {router} from "@/router";

axios.defaults.crossDomain = true

export const requests = axios.create({
    baseURL: 'http://localhost:8081',
    timeout: 10000, // 请求超时时间
    headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
    }
});

// 请求拦截器
requests.interceptors.request.use(
    (config) => {
        // 在发送请求之前做些什么
        const token = localStorage.getItem('token'); // 假设token存储在localStorage中
        if (token) {
            config.headers.Token = `Bearer ${token}`; // 添加token到请求头
        }
        return config;
    },
    (error) => {
        // 对请求错误做些什么
        return Promise.reject(error);
    }
);

// 响应拦截器
requests.interceptors.response.use(
    (response) => {
        console.log(response.data.code)
        const code=response.data.code
        if (code === 5006 ||code===5007|| code===5008) {
            //返回登录界面
            router.push('/').then(r =>{
                location.reload()
            } )
            // router.go(0)
            //删除当前localStorage中的token
            localStorage.removeItem("token")
        }

        return response.data;
    },
    (error) => {
        // 对响应错误做点什么
        return Promise.reject(error);
    }
);

注意请求拦截器,每次发起请求时,都会从localStorage中取出token,并将其放入headers中

三、测试

集成了redis后,记得先开启redis服务,在登录时,框架会自动保存数据

  1. 开启前后端服务

    image-20240523133544450

    本文的代码是从完整项目中抽出的,上述代码只有登录功能

    image-20240523133614619

输入用户名和密码后,进入主界面

image-20240523134210128

此时,登录的信息已经自动的被框架自动保存进redis

image-20240523134056069

IDEA控制台打印出相应的信息

image-20240523134140772

参考资料

Sa-Token

使用 Sa-Token 的全局过滤器解决跨域问题(三种方式全版) - 掘金 (juejin.cn)

Sa-Token功能结构图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/635423.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解除网页禁止选择

控制台输入以下命令 复制&#xff1a;javascript:void(document.body.οncοpy) 可选&#xff1a;javascript:void(document.body.onselectstart) 拖拉&#xff1a;javascript:void(document.body.οnmοuseup)

短剧系统源码解析与应用

在数字化时代&#xff0c;短剧作为一种新兴的娱乐形式&#xff0c;因其内容紧凑、节奏快速而受到广大年轻群体的喜爱。短剧系统源码的开发和应用&#xff0c;不仅为创作者提供了一个展示才华的平台&#xff0c;也为观众带来了全新的观看体验。本文将对短剧系统源码进行解析&…

MVSnet 代码详解(pytorch)

大致过一下MVSnet 论文中核心的点对应代码应该怎么写。 forward 函数需要 照片&#xff0c;映射矩阵&#xff0c;以及深度值。 照片的shape是 &#xff08;1&#xff0c;5,3&#xff0c;1184,1600&#xff09;代表着1个batch,5张图片&#xff0c;然后一次是每张图片的channel和…

【软考】设计模式之装饰器模式

目录 1. 说明2. 应用场景3. 结构图4. 构成5. 适用性6. 优点7. 缺点8. java示例 1. 说明 1.动态地给一个对象添加一些额外的职责。2.Decorator Pattern。3.就增加功能而言&#xff0c;装饰器模式比生成子类更加灵活。4.一种在不改变现有对象结构的情况下&#xff0c;动态地给对…

quartz定时任务

Quartz 数据结构 quartz采用完全二叉树&#xff1a;除了最后一层每一层节点都是满的&#xff0c;而且最后一层靠左排列。 二叉树节点个数规则&#xff1a;每层从左开始&#xff0c;第一层只有一个&#xff0c;就是2的0次幂&#xff0c;第二层两个就是2的1次幂&#xff0c;第三…

Go 和 Delphi 定义可变参数函数的对比

使用可变参数函数具有灵活性、重用性、简化调用等优点&#xff0c;各个语言有各自定义可变参数函数的方法&#xff0c;也有通用的处理方法&#xff0c;比如使用数组、定义参数结构体、使用泛型等。 这里总结记录一下 go、delphi 的常用的定义可变参数函数的方式&#xff01; 一…

Docker安装MongoDB(Linux版)

文章目录 前言一、Docker环境的准备1.安装依赖2.安装Docker 二、使用Docker安装MongoDB1.mongo版本选取2.拉取合适的镜像3.宿主机创建MongoDB需要挂载的文件夹4.第一次无认证创建mongo用户5.启动需要认证的mongo容器 问题汇总总结 前言 本文章主要介绍在Centos系统&#xff0c…

delphi fmx 跨平台文件浏览器

很多人在找delphi fmx 开发的 android下的文件浏览器 现在她来了 支持android,ios android12 测试通过 代码: object Form1: TForm1Left = 0Top = 0Caption = Form1ClientHeight = 549ClientWidth = 340FormFactor.Width = 320FormFactor.Height = 480FormFactor.Dev…

【GDAL】GDAL库学习(C#版本)

1.GDAL 2.VS2022配置GDAL环境&#xff08;C#&#xff09; VS2022工具–NuGet包管理器–管理解决方案的NuGet程序包&#xff0c;直接安装GDAL包。 并且直接用应用到当前的控制台程序中。 找一张tiff格式的图片&#xff0c;或者用格式转换网站&#xff1a;https://www.zamzar.c…

Web前端开发技术、详细文章、(例子)html 列表、有序列表、无序列表、列表嵌套

目录 列表概述 列表类型与标记符号 无序列表 语法&#xff1a; 语法说明&#xff1a; 无序列表标记的 type 属性及其说明 代码解释 有序列表 基本语法 属性说明 1、列表 o1标记的属性 2、列表项li标记的属性 有序列表 o1标记的属性、值 代码解释 列表嵌套 基本…

【Qt】深入探索Qt主窗口与菜单栏:构建高效用户界面的实践指南

文章目录 前言1. 什么是Main Window?2. 详细了解一下其中的 菜单栏&#xff1a;2.1. 创建菜单栏2.2. 添加快捷键2.3. 添加子菜单2.4. 添加分割线2.5. 添加图标 3. 内存泄漏问题&#xff1a;总结 前言 在现代软件开发中&#xff0c;用户界面的设计对于提升用户体验至关重要。Q…

秀某动预约抢票脚本

秀某动预约抢票脚本 小白操作-仅供学习参考 主要流程和功能 初始化和配置变量: confirm_url 和 login_url: 分别存储登录和确认订单的URL。 wait_time: 用户输入的提前多少秒开始执行。 start_time: 开售时间。 DEBUG: 调试标志&#xff0c;用于控制脚本的行为。 浏览…

并网逆变器学习笔记9---VSG控制

参考文献&#xff1a;《新型电力系统主动构网机理与技术路径》 “构网技术一般包含下垂控制&#xff0c;功率同步控制&#xff0c;虚拟同步机控制&#xff0c;直接功率控制&#xff0c;虚拟振荡器控制 等。其中&#xff0c;虚拟同步机技术&#xff0c;即 VSG&#xff0c;因其物…

css扇形菜单动画效果

菜单组件 IntelligentAnalysis.vue 中间圆形区域可以换个图片 <template><div class"intel-analysis"><div class"info" :class"{ close-animation: !showMenu }"><div class"middle"></div><div cl…

协变(List泛型作为方法参数时的父类子类问题)

有段时间没搞.net的项目了&#xff08;没办法&#xff0c;谁让国内JAVA流行是事实&#xff09;。最近又回归.net&#xff08;哪里需要哪里搬~&#xff09;。 接收到需求后&#xff0c;一顿输出&#xff0c;结果…咦?编译失败??? 错误信息&#xff1a; CS1503:参数1:无法…

阿里云 EMR Serverless Spark 版开启免费公测

阿里云 EMR Serverless Spark 版是一款云原生&#xff0c;专为大规模数据处理和分析而设计的全托管 Serverless 产品。它为企业提供了一站式的数据平台服务&#xff0c;包括任务开发、调试、调度和运维等&#xff0c;极大地简化了数据处理的全生命周期工作流程。使用 EMR Serve…

win11安装MySQL

目录[-] 1. 1. 下载2. 2. 安装 参考文档&#xff1a;MySQL :: MySQL 8.4 Reference Manual 1. 下载 mysql官网下载msi安装程序&#xff1a;MySQL :: Begin Your Download 2. 安装 运行下载的mis程序,逐步安装。 安装模式&#xff1a; complete; 进入配置&#xff1a; data di…

轻量SEO分析报告程序网站已开心去授权

轻量SEO分析报告程序网站已开心去授权&#xff0c;可以让你生成有洞察力的、 简洁的、易于理解的SEO报告&#xff0c;帮助你的网页排名和表现更好 网站源码免费下载地址抄笔记 (chaobiji.cn)https://chaobiji.cn/

苹果CMS:如何去掉首页帮助提示信息

首先我们安装好苹果CMS&#xff0c;未安装的可以参考苹果cms&#xff1a;介绍及安装 安装好之后我们需要进入模版设置&#xff0c;可能对于刚刚接触CMS框架的朋友是不清楚地址的&#xff1a; https://www.yourweb.com/admin_login.php/admin/mxpro/mxproset 其中【yourweb】…

探索移动云:我的ES与Kibana之旅

目录 引言&#xff1a; 如何免费体验移动云产品 登录并完成实名认证 选择试用ECS云主机 安全组配置 安装Elasticsearch和Kibana 安装Elasticsearch ​编辑安装kibana 测试结果 使用感觉 引言&#xff1a; 移动云技术产品的发展已经取得了巨大的进步。云数融合、A1、大…