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

news/2025/1/11 12:12:18/

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中读取tokenis-read-cookie: false# 是否从请求体里读取tokenis-read-body: false# 是否从header中读取tokenis-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 {@Autowiredprivate 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;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注册分页拦截器registry.addInterceptor(new PageableInterceptor());// 注册Redis限流器registry.addInterceptor(accessLimitInterceptor);// 注册 Sa-Token 的注解拦截器,打开注解式鉴权功能registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");}@Beanpublic 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 {@Autowiredprivate UserMapper userMapper;@Autowiredprivate HttpServletRequest request;/*** 每次登录时触发*/@Overridepublic 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和osMap<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);// 用户在线信息存入tokenSessionSaSession tokenSession = StpUtil.getTokenSessionByToken(tokenValue);tokenSession.set(ONLINE_USER, onlineUserResp);}/*** 每次注销时触发*/@Overridepublic void doLogout(String loginType, Object loginId, String tokenValue) {// 删除缓存中的用户信息StpUtil.logoutByTokenValue(tokenValue);}/*** 每次被踢下线时触发*/@Overridepublic void doKickout(String loginType, Object loginId, String tokenValue) {}/*** 每次被顶下线时触发*/@Overridepublic void doReplaced(String loginType, Object loginId, String tokenValue) {}/*** 每次被封禁时触发*/@Overridepublic void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {}/*** 每次被解封时触发*/@Overridepublic void doUntieDisable(String loginType, Object loginId, String service) {}/*** 每次二级认证时触发*/@Overridepublic void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {}/*** 每次退出二级认证时触发*/@Overridepublic void doCloseSafe(String loginType, String tokenValue, String service) {}/*** 每次创建Session时触发*/@Overridepublic void doCreateSession(String id) {}/*** 每次注销Session时触发*/@Overridepublic void doLogoutSession(String id) {}/*** 每次Token续期时触发*/@Overridepublic 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 {@Autowiredprivate MenuMapper menuMapper;@Autowiredprivate RoleMapper roleMapper;/*** 返回一个账号所拥有的权限码集合** @param loginId   登录用户id* @param loginType 登录账号类型* @return 权限集合*/@Overridepublic 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 角色集合*/@Overridepublic List<String> getRoleList(Object loginId, String loginType) {SaSession session = StpUtil.getSessionByLoginId(loginId);return session.get("Role_List", () -> roleMapper.selectRoleListByUserId(loginId));}}

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

相关文章

Java爬取1688商品详情API接口

在当今的电商时代&#xff0c;1688平台凭借其庞大的商品种类和丰富的供应商资源&#xff0c;成为了众多企业和个人获取商品信息的重要渠道。对于开发者而言&#xff0c;能够通过API接口爬取1688商品的详细信息&#xff0c;将极大地助力于数据分析、市场调研、价格监控等多方面的…

npm-npm install时rollbackFailedOptional: verb npm-session ce210dc17dd264aa报错

1.前言 npm install的时候卡着不动&#xff0c;安装失败。 2.解决 2.1清除代理 npm config rm proxy npm config rm https-proxy #权限问题记得加sudo2.2修改镜像资源为淘宝镜像资源 npm config set registry http://registry.npm.taobao.org2.3查看镜像资源是否切换成功 …

【漫话机器学习系列】041.信息丢失(dropout)

信息丢失&#xff08;Dropout&#xff09; Dropout 是一种广泛应用于神经网络训练中的正则化技术&#xff0c;旨在减少过拟合&#xff08;overfitting&#xff09;&#xff0c;提高模型的泛化能力。虽然"信息丢失"&#xff08;dropout&#xff09;这个术语在某些情况…

需求:h5和小程序预览图片需要有当前第几张标识

1.小程序直接使用api&#xff1a;uni.previewImage 2.h5使用轮播图写一个组件 <template><view class"custom-image-preview" v-if"visible"><view class"overlay"></view><swiper class"swiper" :curre…

Audio音频输出通道

一、AudioTrack的使用 //设置音频属性 AudioAttributes audioAttributes new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).setUsage(AudioAttributes.USAGE_MEDIA).build(); //设置音频格式 AudioFormat audioFormat new AudioFormat.Bu…

15个学习Python 的编程游戏网站

从小很多人都会在想&#xff0c;那些枯燥的教学课程要是全部变成游戏就好了&#xff0c;这样的话那期末成绩不得立即起飞了嘛&#xff1f;那对于编程很多人也有这样的想法&#xff0c;边玩边学就好了 这不已经有很多程序员开发了多款边玩边学的编程游戏供大家使用&#xff0c;…

了解RabbitMQ的工作原理

RabbitMQ是一个开源的消息代理系统&#xff0c;实现了高级消息队列协议&#xff08;AMQP&#xff09;。在现代分布式系统中&#xff0c;特别是在微服务架构中&#xff0c;RabbitMQ有广泛的应用。本文将详细介绍RabbitMQ的工作原理&#xff0c;并通过实践案例帮助读者理解和应用…

16_TypeScript 泛型 --[深入浅出 TypeScript 测试]

TypeScript 的泛型&#xff08;Generics&#xff09;是允许我们在定义函数、接口或类的时候&#xff0c;不预先指定具体类型&#xff0c;而是在使用时再指定类型的机制。这为代码提供了更大的灵活性和复用性&#xff0c;同时保持了类型安全。 泛型标识符 在 TypeScript 中&am…