后端技术选型 sa-token校验学习 上 登录校验复习

sa-token 的官网

Sa-Token

复习

首先我们要明确一下 cookie 是什么

登录校验 Sa-Token 官方文档里面的

对于一些登录之后才能访问的接口(例如:查询我的账号资料),我们通常的做法是增加一层接口校验:

  • 如果校验通过,则:正常返回数据。
  • 如果校验未通过,则:抛出异常,告知其需要先进行登录。

那么,判断会话是否登录的依据是什么?我们先来简单分析一下登录访问流程:

  1. 用户提交 name + password 参数,调用登录接口。
  2. 登录成功,返回这个用户的 Token 会话凭证。
  3. 用户后续的每次请求,都携带上这个 Token。
  4. 服务器根据 Token 判断此会话是否登录成功。

所谓登录认证,指的就是服务器校验账号密码,为用户颁发 Token 会话凭证的过程,这个 Token 也是我们后续判断会话是否登录的关键所在。

sa-token

// 当前会话注销登录
StpUtil.logout();

// 获取当前会话是否已经登录,返回true=已登录,false=未登录
StpUtil.isLogin();

// 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.checkLogin();

HTTP cookie,简称cookie,又称数码存根、“网站/浏览+魔饼/魔片”等,是浏览网站时由网络服务器创建并由网页浏览器存放在用户计算机或其他设备的小文本文件

登录后 都在浏览器保存一份 cookie

postman 也是类似

springboot 配置

# Sa-Token 配置 (文档: https://sa-token.cc)
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: Authorization
  # token前缀
  token-prefix: Bearer
  # token有效期,单位s 默认30天, -1代表永不过期
  timeout: 1800
  # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
  active-timeout: -1
  # 关闭自动续签
  auto-renew: false
  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
  is-concurrent: true
  # token风格
  token-style: uuid
  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
  is-share: false
  # 同一账号最大登录数量
  max-login-count: 20
  # 是否从cookie中读取token
  is-read-cookie: false
  # 是否从请求体里读取token
  is-read-body: false
  # 是否从header中读取token
  is-read-header: true
  # 是否输出操作日志
  is-log: false

注意这个输出操作日志必须是 false

不知道为什么 如果开成 true 会出现莫名其妙的报错

Authorization: Bearer是干什么的?底层原理是什么?

底层原理是这样的:当客户端发送 HTTP 请求时,可以在请求头部中添加 "Authorization" 字段来传递访问令牌。"Bearer" 是一种认证方案(authentication scheme)的名称,用于指示后面的令牌是访问令牌。

例如,如果你有一个名为 "your_access_token" 的访问令牌,你可以通过设置请求头部的 "Authorization" 字段来传递它:

Authorization: Bearer your_access_token

服务器在接收到请求时,可以读取 "Authorization" 字段,并解析出令牌部分,即 "your_access_token"。然后,服务器可以使用该令牌进行身份验证和授权操作。

使用 "Authorization: Bearer" 的形式可以带来一些好处:

  1. 一致性:它遵循了 HTTP 规范中关于认证方案的标准格式,使得在不同的系统和框架之间可以更好地进行互操作性。
  2. 可扩展性:"Bearer" 方案可以与不同类型的令牌一起使用,如基于 JSON Web Token (JWT) 的令牌,OAuth 2.0 的访问令牌等。
  3. 安全性:通过将访问令牌放置在请求头部中,可以避免令牌泄露在 URL 参数或请求主体中的潜在风险。

需要注意的是,"Bearer" 方案本身并不提供加密或验证令牌的机制,它只是一种用于标识令牌类型的约定。实际的令牌验证和授权逻辑需要在服务器端进行,根据具体的身份验证和授权方案进行处理。

总结来说,"Authorization: Bearer" 是一种在 HTTP 请求头部中用于传递访问令牌的格式。它指示后面的令牌是访问令牌,服务器可以读取并使用该令牌进行身份验证和授权操作。它提供了一种标准化和可扩展的方式来传递访问令牌,并提高了安全性和互操作性。

项目实战

sa-token 的配置类

package com.ican.satoken;

import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import cn.hutool.json.JSONUtil;
import com.ican.interceptor.AccessLimitInterceptor;
import com.ican.interceptor.PageableInterceptor;
import com.ican.model.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import static com.ican.enums.StatusCodeEnum.UNAUTHORIZED;

/**
 * SaToken配置
 *
 * @author Dduo
 * @date 2024/11/28 22:12
 **/
@Slf4j
@Component
public class SaTokenConfig implements WebMvcConfigurer {

    @Autowired
    private AccessLimitInterceptor accessLimitInterceptor;

    private final String[] EXCLUDE_PATH_PATTERNS = {
            "/swagger-resources",
            "/webjars/**",
            "/v2/api-docs",
            "/doc.html",
            "/favicon.ico",
            "/login",
            "/oauth/*",
    };

    private final long timeout = 600;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册分页拦截器
        registry.addInterceptor(new PageableInterceptor());
        // 注册Redis限流器
        registry.addInterceptor(accessLimitInterceptor);
        // 注册 Sa-Token 的注解拦截器,打开注解式鉴权功能
        registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
    }

    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()
                // 拦截路径
                .addInclude("/**")
                // 放开路径
                .addExclude(EXCLUDE_PATH_PATTERNS)
                // 前置函数:在每次认证函数之前执行
                .setBeforeAuth(obj -> {
                    SaHolder.getResponse()
                            // 允许指定域访问跨域资源
                            .setHeader("Access-Control-Allow-Origin", "*")
                            // 允许所有请求方式
                            .setHeader("Access-Control-Allow-Methods", "*")
                            // 有效时间
                            .setHeader("Access-Control-Max-Age", "3600")
                            // 允许的header参数
                            .setHeader("Access-Control-Allow-Headers", "*");
                    // 如果是预检请求,则立即返回到前端
                    SaRouter.match(SaHttpMethod.OPTIONS)
                            .free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
                            .back();
                })
                // 认证函数: 每次请求执行
                .setAuth(obj -> {
                    // 检查是否登录
                    SaRouter.match("/admin/**").check(r -> StpUtil.checkLogin());
                    // 刷新token有效期
                    if (StpUtil.getTokenTimeout() < timeout) {
                        StpUtil.renewTimeout(1800);
                    }
                    // 输出 API 请求日志,方便调试代码
                    SaManager.getLog().debug("请求path={}  提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
                })
                //  异常处理函数:每次认证函数发生异常时执行此函数
                .setError(e -> {
                    // 设置响应头
                    SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
                    if (e instanceof NotLoginException) {
                        // todo 确实是这边有问题
                        e.printStackTrace();
                        return JSONUtil.toJsonStr(Result.fail(UNAUTHORIZED.getCode(), UNAUTHORIZED.getMsg()));
                    }
                    // TODO 服务器后端在这里无法捕获异常,仅仅将异常信息传给了前端
                    e.printStackTrace();
                    return SaResult.error(e.getMessage());
                });
    }

}

自定义侦听器的实现

package com.ican.satoken;

import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ican.entity.User;
import com.ican.mapper.UserMapper;
import com.ican.model.vo.response.OnlineUserResp;
import com.ican.utils.IpUtils;
import com.ican.utils.UserAgentUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Map;

import static com.ican.constant.CommonConstant.ONLINE_USER;
import static com.ican.enums.ZoneEnum.SHANGHAI;

/**
 * 自定义侦听器的实现
 *
 * @author Dduo
 */
@Component
public class MySaTokenListener implements SaTokenListener {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private HttpServletRequest request;

    /**
     * 每次登录时触发
     */
    @Override
    public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
        // 查询用户昵称
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .select(User::getAvatar, User::getNickname)
                .eq(User::getId, loginId));
        // 解析browser和os
        Map<String, String> userAgentMap = UserAgentUtils.parseOsAndBrowser(request.getHeader("User-Agent"));
        // 获取登录ip地址
        String ipAddress = ServletUtil.getClientIP(request);
        // 获取登录地址
        String ipSource = IpUtils.getIpSource(ipAddress);
        // 获取登录时间
        LocalDateTime loginTime = LocalDateTime.now(ZoneId.of(SHANGHAI.getZone()));
        OnlineUserResp onlineUserResp = OnlineUserResp.builder()
                .id((Integer) loginId)
                .token(tokenValue)
                .avatar(user.getAvatar())
                .nickname(user.getNickname())
                .ipAddress(ipAddress)
                .ipSource(ipSource)
                .os(userAgentMap.get("os"))
                .browser(userAgentMap.get("browser"))
                .loginTime(loginTime)
                .build();
        // 更新用户登录信息
        User newUser = User.builder()
                .id((Integer) loginId)
                .ipAddress(ipAddress)
                .ipSource(ipSource)
                .loginTime(loginTime)
                .build();
        userMapper.updateById(newUser);
        // 用户在线信息存入tokenSession
        SaSession tokenSession = StpUtil.getTokenSessionByToken(tokenValue);
        tokenSession.set(ONLINE_USER, onlineUserResp);
    }

    /**
     * 每次注销时触发
     */
    @Override
    public void doLogout(String loginType, Object loginId, String tokenValue) {
        // 删除缓存中的用户信息
        StpUtil.logoutByTokenValue(tokenValue);
    }

    /**
     * 每次被踢下线时触发
     */
    @Override
    public void doKickout(String loginType, Object loginId, String tokenValue) {
    }

    /**
     * 每次被顶下线时触发
     */
    @Override
    public void doReplaced(String loginType, Object loginId, String tokenValue) {

    }

    /**
     * 每次被封禁时触发
     */
    @Override
    public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {

    }

    /**
     * 每次被解封时触发
     */
    @Override
    public void doUntieDisable(String loginType, Object loginId, String service) {

    }

    /**
     * 每次二级认证时触发
     */
    @Override
    public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {

    }

    /**
     * 每次退出二级认证时触发
     */
    @Override
    public void doCloseSafe(String loginType, String tokenValue, String service) {

    }

    /**
     * 每次创建Session时触发
     */
    @Override
    public void doCreateSession(String id) {

    }

    /**
     * 每次注销Session时触发
     */
    @Override
    public void doLogoutSession(String id) {

    }

    /**
     * 每次Token续期时触发
     */
    @Override
    public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {

    }
}

自定义权限验证接口扩展

package com.ican.satoken;

import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.SaSessionCustomUtil;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import com.ican.mapper.MenuMapper;
import com.ican.mapper.RoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * 自定义权限验证接口扩展
 *
 * @author Dduo
 */
@Component
public class StpInterfaceImpl implements StpInterface {

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private RoleMapper roleMapper;

    /**
     * 返回一个账号所拥有的权限码集合
     *
     * @param loginId   登录用户id
     * @param loginType 登录账号类型
     * @return 权限集合
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // 声明权限码集合
        List<String> permissionList = new ArrayList<>();
        // 遍历角色列表,查询拥有的权限码
        for (String roleId : getRoleList(loginId, loginType)) {
            SaSession roleSession = SaSessionCustomUtil.getSessionById("role-" + roleId);
            List<String> list = roleSession.get("Permission_List", () -> menuMapper.selectPermissionByRoleId(roleId));
            permissionList.addAll(list);
        }
        // 返回权限码集合
        return permissionList;
    }

    /**
     * 返回一个账号所拥有的可用角色标识集合
     *
     * @param loginId   登录用户id
     * @param loginType 登录账号类型
     * @return 角色集合
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        SaSession session = StpUtil.getSessionByLoginId(loginId);
        return session.get("Role_List", () -> roleMapper.selectRoleListByUserId(loginId));
    }

}

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

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

相关文章

【教程】Unity 本地化多语种 | Localization 工具组

开发平台&#xff1a;Unity 6.0 编程平台&#xff1a;Visual Studio 2022 编程语言&#xff1a;CSharp 6.0 工具包类&#xff1a;Localization   一、前言 本地化多语言类型是软件面向国际化所必须的功能项。Unity 在 2022 版本后推出 Localization 工具包&#xff0c;以降低…

matlab编写分段Hermite插值多项式

文章目录 原理使用分段Hermite插值多项式原因公式第一类的两个插值积函数第二类的两个插值积函数 例题法一法二 代码分段 Hermite 插值的思路&#xff1a;分段 Hermite 插值多项式的构造&#xff1a;MATLAB 实现代码&#xff1a;结果如图&#xff1a;注归一化变量的作用&#x…

Elasticsearch:优化的标量量化 - 更好的二进制量化

作者&#xff1a;来自 Elastic Benjamin Trent 在这里&#xff0c;我们解释了 Elasticsearch 中的优化标量量化以及如何使用它来改进更好的二进制量化 (Better Binary Quantization - BBQ)。 我们的全新改进版二进制量化 (Better Binary Quantization - BBQ) 索引现在变得更强大…

【数据库】六、数据库设计

文章目录 六、数据库设计1 数据库设计步骤1.1 规划阶段1.2 需求分析1.3 概念设计阶段(重点)1.4 逻辑设计阶段(重点)1.5 物理设计阶段1.6 数据库的实现1.7 数据库运行与维护 2 概念模型设计2.1 ER模型2.1.1 ER模型的基本元素2.1.2 联系的设计2.1.3 采用ER模型的概念设计2.1.4 ER…

onLoad 生命周期函数是否执行取决于跳转的方式和小程序的页面栈管理机制

文章目录 1. 页面跳转方式2. 你的场景分析3. 页面生命周期4. 总结5. 建议 在微信小程序中&#xff0c;页面跳转时&#xff0c; onLoad 生命周期函数是否执行取决于跳转的方式和小程序的页面栈管理机制。以下是详细说明&#xff1a; 1. 页面跳转方式 微信小程序提供了多种页面…

51c~Pytorch~合集4

我自己的原文哦~ https://blog.51cto.com/whaosoft/12311033 一、Pytorch~训练-使用 这里介绍了Pytorch中已经训练好的模型如何使用 Pytorch中提供了很多已经在ImageNet数据集上训练好的模型了&#xff0c;可以直接被加载到模型中进行预测任务。预训练模型存放在Pytorch的…

深度学习的原理和应用

一、深度学习的原理 深度学习是机器学习领域的一个重要分支&#xff0c;其原理基于多层神经网络结构和优化算法。以下是深度学习的核心原理&#xff1a; 多层神经网络结构&#xff1a;深度学习模型通常由多层神经元组成&#xff0c;这些神经元通过权重和偏置相互连接。输入数据…

基于人工智能的公司logo设计生成方法

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经深入到我们生活的方方面面。其中&#xff0c;基于AI的公司logo设计生成方法&#xff0c;不仅为品牌形象的塑造提供了新的思路&#xff0c;也为企业带来了前所未有的设计体验。本文将详细探讨这一新兴的、…

BO-CNN-BiLSTM-Multihead-Attention,贝叶斯优化CNN-BiLSTM融合多头注意力机制多变量回归预测

BO-CNN-BiLSTM-Multihead-Attention&#xff0c;贝叶斯优化CNN-BiLSTM融合多头注意力机制多变量回归预测 目录 BO-CNN-BiLSTM-Multihead-Attention&#xff0c;贝叶斯优化CNN-BiLSTM融合多头注意力机制多变量回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Ma…

WPF系列九:图形控件EllipseGeometry

简介 EllipseGeometry用于绘制一个椭圆的形状。它通常与其他图形元素结合使用&#xff0c;比如 Path 或者作为剪切区域来定义其他元素的外形。 定义椭圆&#xff1a;EllipseGeometry 用来定义一个椭圆或者圆的几何形状。参与绘制&#xff1a;可以被用作 Path 元素的数据&…

ue5动画重定向,一键重定向。ue4小白人替换成ue5

这就是我们下载的 初学者动画内容包 点击设置选中列 绿色的是动画 黄色的关卡 蓝色是蓝图 ctrla 全选 ctrl鼠标左键 选中所有动画 重定向动画资产 不要选错&#xff0c;只要绿色 选择目标网格体 选择所有的绿色 动画 导出动画 添加前缀ycn 导出 一定要提前新建好存放的…

服务器漏洞修复解决方案

漏洞1、远程桌面授权服务启用检测【原理扫描】 Windows Remote Desktop Licensing Service is running: Get Server version: 0x60000604 1、解决方案&#xff1a;建议禁用相关服务避免目标被利用 方法一&#xff1a;使用服务管理器 打开“运行”对话框&#xff08;WinR&am…

uniapp 微信小程序内嵌h5实时通信

描述&#xff1a; 小程序webview内嵌的h5需要向小程序实时发送消息&#xff0c;有人说postMessage可以实现&#xff0c;所以试验一下&#xff0c;结果是实现不了实时&#xff0c;只能在特定时机后退、组件销毁、分享时小程序才能接收到信息&#xff08;小程序为了安全等考虑做了…

案例解读 | 香港某多元化综合金融企业基础监控+网管平台建设实践

PART01 项目背景 01客户简介案例客户是一家创立20多年的香港某多元化综合金融企业&#xff0c;其业务范围涵盖证券、期货、资产管理、财富管理等&#xff0c;凭借广泛的业务网络和多元化的金融服务产品&#xff0c;在市场中拥有显著的影响力。02痛点分析随着业务版图的持续拓展…

LabVIEW实现动态水球图的方法

水球图是一种直观展示百分比数据的图表&#xff0c;常用于数据监测与展示。LabVIEW 虽不直接支持水球图绘制&#xff0c;但可通过图片控件动态绘制波形&#xff0c;或借助 HTMLCSS 的 Web 控件实现。此外&#xff0c;还可以结合 Python 等第三方工具生成水球图&#xff0c;LabV…

Simulink中的正弦波模块学习【Sine Wave】

Simulink中的正弦波模块学习 Simulink库中的Sine Wave模块 如下图所示为MATLAB Simulink中的正弦波模块 其元器件库位置为Simulink→Sources→Sine Wave 各项设置参数如下, 点击Help可查看详细信息 正弦波的数学表达式为 y A sin ⁡ ( ω x φ ) k y A \sin(\omega x \va…

maven高级(day15)

Maven 是一款构建和管理 Java 项目的工具 分模块设计与开发 所谓分模块设计&#xff0c;顾名思义指的就是我们在设计一个 Java 项目的时候&#xff0c;将一个 Java 项目拆分成多 个模块进行开发。 分模块设计我们在进行项目设计阶段&#xff0c;就可以将一个大的项目拆分成若干…

Vite源码学习分享(一)

!](https://i-blog.csdnimg.cn/direct/971c35b61c57402b95be91d2b4965d85.png) 同一个项目 vite VS webpack启动速度对比

C#里使用libxl设置EXCEL里公式计算的例子

在EXCEL的使用里,经常使用的是公式功能, 为什么会这样说呢? 因为公式是一种自动化计算工具,并且可以固化人类的智慧,相当于把复杂的计算功能嵌入到固定的数据处理了。比如一个经验丰富的财务人员,可以编制一个复杂公式计算的表格,只要一个不懂财务的人员,输入每个人的…

使用证件照制作软件的常见问题及解决方案

在数字化时代&#xff0c;证件照的制作变得越来越简单。借助各种证件照制作软件&#xff0c;我们可以轻松在家中制作出符合要求的证件照。然而&#xff0c;用户在使用这些软件时&#xff0c;可能会遇到一些常见问题。为了帮助您顺利制作出满意的证件照&#xff0c;我们整理了一…