首页 > 数据库 > 数据库开发技术 > 集成sa-token实现登录和RBAC权限控制

https://sa-token.cc/v/v1.36.0/doc.html#/
Sa-Token 目前主要五大功能模块:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权。

<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot-starter</artifactId><version>1.37.0</version></dependency><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-jwt</artifactId><version>1.37.0</version></dependency><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-redis-jackson</artifactId><version>1.37.0</version></dependency><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-aop</artifactId><version>1.37.0</version></dependency>
sa-token:token-name: satokentoken-prefix: Bearertimeout: 86400active-timeout: -1is-concurrent: trueis-share: falsetoken-style: uuidis-log: truejwt-secret-key: adfdfdsdasdasifdfdffhueuiwyudfdfddfdfsfsdfrfewbfjsdafjk
https://sa-token.cc/v/v1.36.0/doc.html#/fun/curr-domain2.3 代码配置
https://sa-token.cc/v/v1.36.0/doc.html#/plugin/aop-at
https://sa-token.cc/v/v1.36.0/doc.html#/use/at-check
#使用 Sa-Token 的全局过滤器解决跨域问题(三种方式全版)
https://juejin.cn/post/7247376558367981627
SaTokenConfigure配置
package xxxxx.config;import cn.dev33.satoken.SaManager;import cn.dev33.satoken.context.SaHolder;import cn.dev33.satoken.exception.SaTokenException;import cn.dev33.satoken.filter.SaServletFilter;import cn.dev33.satoken.jwt.StpLogicJwtForSimple;import cn.dev33.satoken.router.SaHttpMethod;import cn.dev33.satoken.router.SaRouter;import cn.dev33.satoken.stp.StpLogic;import cn.dev33.satoken.stp.StpUtil;import xxxxx.RestResponse;import lombok.extern.slf4j.Slf4j;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;public class SaTokenConfigure implements WebMvcConfigurer {// Sa-Token 整合 jwt (Simple 简单模式)public StpLogic getStpLogicJwt() {return new StpLogicJwtForSimple();}/*** 注册 [Sa-Token 全局过滤器]*/public SaServletFilter getSaServletFilter() {return new SaServletFilter()// 指定 [拦截路由] 与 [放行路由].addInclude("/**")// 登录认证 -- 拦截所有路由,并排除/user/login 用于开放登录.addExclude("/user/**").addExclude("/favicon.ico").addExclude("*.js").addExclude("*.css")// 认证函数: 每次请求执行.setAuth(obj -> {SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());// ...SaRouter.match("/**") // 拦截的 path 列表,可以写多个.check(r -> StpUtil.checkLogin());// 要执行的校验动作,可以写完整的 lambda 表达式// 根据路由划分模块,不同模块不同鉴权SaRouter.match("/xxx/xxxx/**", r -> StpUtil.checkPermission("xxxx.xxx"));,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,// 更多拦截处理方式,请参考“路由拦截式鉴权”章节 */})// 异常处理函数:每次认证函数发生异常时执行此函数.setError(e1 -> {log.error("sa-token异常:{}", e1.getMessage());// 设置响应头SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");/*** sa-token登录相关异常处理* https://sa-token.cc/v/v1.36.0/doc.html#/fun/exception-code*/if (e1 instanceof SaTokenException) {SaTokenException e = (SaTokenException) e1;// 根据不同异常细分状态码返回不同的提示if (e.getCode() == 11001) {return RestResponse.fail("未能读取到有效Token");}if (e.getCode() == 11002) {return RestResponse.fail("登录时的账号为空");}if (e.getCode() == 11011) {return RestResponse.fail("未能读取到有效Token");}if (e.getCode() == 11012) {return RestResponse.fail("Token无效");}if (e.getCode() == 11013) {return RestResponse.fail("Token已过期");}if (e.getCode() == 11014) {return RestResponse.fail("Token已被顶下线");}if (e.getCode() == 11015) {return RestResponse.fail("Token已被踢下线");}if (e.getCode() == 11016) {return RestResponse.fail("Token已被冻结");}if (e.getCode() == 11017) {return RestResponse.fail("未按照指定前缀提交token");}if (e.getCode() == 11041) {return RestResponse.fail("缺少指定的角色");}if (e.getCode() == 11051) {return RestResponse.fail("缺少指定的权限");}if (e.getCode() == 11061) {return RestResponse.fail("当前账号未通过服务封禁校验");}if (e.getCode() == 11062) {return RestResponse.fail("提供要解禁的账号无效");}if (e.getCode() == 12001) {return RestResponse.fail("请求中缺少指定的参数");}if (e.getCode() == 12111) {return RestResponse.fail("密码md5加密异常");}if (e.getCode() == 30201) {return RestResponse.fail("对jwt字符串解析失败");}if (e.getCode() == 30202) {return RestResponse.fail("此jwt的签名无效");}if (e.getCode() == 30203) {return RestResponse.fail("此jwt的loginType字段不符合预期");}if (e.getCode() == 30204) {return RestResponse.fail("此jwt已超时");}if (e.getCode() == 30205) {return RestResponse.fail("没有配置jwt秘钥");}if (e.getCode() == 30206) {return RestResponse.fail("登录时提供的账号为空");}// 更多 code 码判断 ...// 默认的提示return RestResponse.fail("登录异常,请联系管理员处理...");}return RestResponse.fail(e1.getMessage());})// 前置函数:在每次认证函数之前执行.setBeforeAuth(obj -> {// ---------- 设置一些安全响应头 ----------SaHolder.getResponse()// 服务器名称//.setServer("sa-server")// 是否可以在iframe显示视图:DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以.setHeader("X-Frame-Options", "SAMEORIGIN")// 是否启用浏览器默认XSS防护:0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面.setHeader("X-XSS-Protection", "1; mode=block")// 禁用浏览器内容嗅探.setHeader("X-Content-Type-Options", "nosniff")// ---------- 设置跨域响应头 ----------// 允许指定域访问跨域资源.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 -> log.info("--------OPTIONS预检请求,不做处理")).back();});}}
自定义权限加载接口实现类:
package xxxxx.config;import cn.dev33.satoken.stp.StpInterface;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.io.Serializable;import java.util.ArrayList;import java.util.List;import java.util.Objects;/*** 自定义权限加载接口实现类* 保证此类被 SpringBoot 扫描,完* 成 Sa-Token 的自定义权限验证扩展*/public class StpInterfaceImpl implements StpInterface {/*** 返回一个账号所拥有的权限码集合*/public List<String> getPermissionList(Object loginId, String loginType) {List<String> permissionList = new ArrayList<>();//TODO 根据登录的loginId(登录用户id)去查权限,可以存缓存中,从缓存中取,权限有变动更新缓存return permissionList;}/*** 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)*/public List<String> getRoleList(Object loginId, String loginType) {List<String> roleList = new ArrayList<>();//TODO 根据登录的loginId(登录用户id)去查角色,可以存缓存,从缓存中取,角色变动更新缓存return roleList;}}
角色表-role表:




with recursive menu_power_tree(id, parent_id, type, name,remarks,source_type,menu_sort,menu_level) AS (-- 初始查询,选择所有没有父级别的分类(即根分类)SELECT id, parent_id, type, name,remarks,source_type,menu_sort,menu_levelFROM dyict_resourceWHERE parent_id = 0 and source_type = 1UNION ALL-- 递归查询,选择所有子分类SELECT c1.id, c1.parent_id, c1.type, c1.name, c1.remarks,c1.source_type,c1.menu_sort,c1.menu_level FROM(SELECT id, parent_id, type, name,remarks,source_type,menu_sort,menu_levelFROM dyict_resource c WHERE c.parent_id <> 0) c1INNER JOIN menu_power_tree ct ON ct.id = c1.parent_id)SELECT a.id, a.parent_id, a.type, a.name,a.remarks,a.source_type,a.menu_sort,a.menu_level,(SELECT count(*) FROM dyict_role_resource_power b WHERE a.id = b.resource_id and b.role_id in(1)) as pFROM menu_power_tree aORDER BY parent_id,id;
基础接口RoleResourcePowerMapper:
public interface RoleResourcePowerMapper extends BaseMapper<RoleResourcePower> {/*** 角色对应的菜单权限用于获取权限和构建权限** @param sourceType* @param roleIds* @return*/List<MenuPowerTreeDto> menuPowerTree(@Param("sourceType") Integer sourceType, @Param("roleIds") List<Integer> roleIds);}
RoleResourcePowerMapper.xml
<select id="menuPowerTree" resultType="xxxx.dto.MenuPowerTreeDto">with recursive menu_power_tree(id, parent_id, type, name,remarks,source_type,menu_sort,menu_level) AS (-- 初始查询,选择所有没有父级别的分类(即根分类)SELECT id, parent_id, type, name,remarks,source_type,menu_sort,menu_levelFROM dyict_resourceWHERE parent_id = 0<if test="sourceType != null">and source_type = #{sourceType}</if>UNION ALL-- 递归查询,选择所有子分类SELECT c1.id, c1.parent_id, c1.type, c1.name, c1.remarks,c1.source_type,c1.menu_sort,c1.menu_level FROM(SELECT id, parent_id, type, name,remarks,source_type,menu_sort,menu_levelFROM dyict_resource c WHERE c.parent_id <![CDATA[<> ]]>0) c1INNER JOIN menu_power_tree ct ON ct.id = c1.parent_id)SELECT a.id, a.parent_id, a.type, a.name,a.remarks,a.source_type,a.menu_sort,a.menu_level,(SELECT count(*) FROM dyict_role_resource_power b WHERE a.id = b.resource_id<if test="roleIds != null and roleIds.size() > 0 ">andb.role_id in<foreach collection="roleIds" item="id" index="index" open="(" close=")" separator=",">#{id}</foreach></if>) as pFROM menu_power_tree aORDER BY parent_id,id</select>
MenuPowerTreeDto类:
package xxxx.dto;import lombok.Data;import java.io.Serializable;public class MenuPowerTreeDto implements Serializable {private static final long serialVersionUID = -8644290706362470684L;private Integer id;private Integer parentId;private Integer type;private String name;private String remarks;private Integer sourceType;private Integer menuSort;private Integer menuLevel;private Integer p;}
MenuPowerTreeVo类:
package xxxx.vo;import lombok.Data;import java.io.Serializable;import java.util.List;public class MenuPowerTreeVo implements Serializable {private static final long serialVersionUID = 3214808951975328795L;private Integer id;private Integer parentId;private Integer type;private String name;private String remarks;private Integer sourceType;private Integer menuSort;private Integer menuLevel;private Boolean hasPower;private List<MenuPowerTreeVo> childrenMenuType;private List<MenuPowerTreeVo> childrenButtonType;}
RoleResourcePowerServiceImpl类:
package xxxxx.service.impl;import cn.hutool.core.collection.CollectionUtil;import com.alibaba.fastjson.JSON;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.BeanUtils;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.Comparator;import java.util.List;import java.util.Objects;import java.util.stream.Collectors;4jpublic class RoleResourcePowerServiceImpl extends ServiceImpl<RoleResourcePowerMapper, RoleResourcePower> implements RoleResourcePowerService {public MenuPowerTreeVo queryMenuPowerTreeVo(Integer sourceType, Integer roleId) {if (Objects.nonNull(sourceType) && Objects.nonNull(roleId)) {List<MenuPowerTreeDto> menuPowerTrees = this.baseMapper.menuPowerTree(sourceType, Arrays.asList(roleId));if (CollectionUtil.isNotEmpty(menuPowerTrees)) {MenuPowerTreeVo menuPowerTreeVo = new MenuPowerTreeVo();List<MenuPowerTreeDto> oneMenuPowerTrees = menuPowerTrees.stream().filter(e -> e.getParentId() == 0).collect(Collectors.toList());if (CollectionUtil.isNotEmpty(oneMenuPowerTrees)) {this.buildeTree(oneMenuPowerTrees, menuPowerTrees, menuPowerTreeVo);if (oneMenuPowerTrees.size() == 1) {List<MenuPowerTreeVo> children = new ArrayList<>();children.add(menuPowerTreeVo);MenuPowerTreeVo menuPowerTreeVo2 = new MenuPowerTreeVo();menuPowerTreeVo2.setId(-1);menuPowerTreeVo2.setName("父节点");menuPowerTreeVo2.setParentId(-1);menuPowerTreeVo2.setMenuSort(0);menuPowerTreeVo2.setChildrenMenuType(children);return menuPowerTreeVo2;}return menuPowerTreeVo;}}}return null;}private void buildeTree(List<MenuPowerTreeDto> oneMenuPowerTrees, List<MenuPowerTreeDto> menuPowerTrees, MenuPowerTreeVo menuPowerTreeVo) {if (oneMenuPowerTrees.size() > 1) {menuPowerTreeVo.setId(-1);menuPowerTreeVo.setName("父节点");menuPowerTreeVo.setParentId(-1);menuPowerTreeVo.setMenuSort(0);List<MenuPowerTreeDto> childrens = oneMenuPowerTrees;this.commonBuildTree(menuPowerTrees, menuPowerTreeVo, childrens);} else if (oneMenuPowerTrees.size() == 1) {for (MenuPowerTreeDto mp : oneMenuPowerTrees) {BeanUtils.copyProperties(mp, menuPowerTreeVo);if (Objects.nonNull(mp.getP()) && mp.getP() > 0) {menuPowerTreeVo.setHasPower(true);}Integer id = mp.getId();List<MenuPowerTreeDto> childrens = menuPowerTrees.stream().filter(e -> Objects.nonNull(e.getParentId()) && e.getParentId() == id).collect(Collectors.toList());this.commonBuildTree(menuPowerTrees, menuPowerTreeVo, childrens);}}}private void commonBuildTree(List<MenuPowerTreeDto> menuPowerTrees, MenuPowerTreeVo menuPowerTreeVo, List<MenuPowerTreeDto> childrens) {if (CollectionUtil.isNotEmpty(childrens)) {List<MenuPowerTreeDto> childrenButtonTypeDto = childrens.stream().filter(e -> Objects.nonNull(e.getType()) && e.getType() == 2).collect(Collectors.toList());if (CollectionUtil.isNotEmpty(childrenButtonTypeDto)) {List<MenuPowerTreeVo> childrenButtonType = new ArrayList<>();for (MenuPowerTreeDto mb : childrenButtonTypeDto) {MenuPowerTreeVo menuPowerTreeVo2 = new MenuPowerTreeVo();BeanUtils.copyProperties(mb, menuPowerTreeVo2);if (Objects.nonNull(mb.getP()) && mb.getP() > 0) {menuPowerTreeVo2.setHasPower(true);}childrenButtonType.add(menuPowerTreeVo2);}menuPowerTreeVo.setChildrenButtonType(childrenButtonType);}List<MenuPowerTreeDto> childrenMenuTypeDto = childrens.stream().filter(e -> Objects.nonNull(e.getType()) && e.getType() == 1).collect(Collectors.toList());if (CollectionUtil.isNotEmpty(childrenMenuTypeDto)) {List<MenuPowerTreeVo> childrenMenuType = new ArrayList<>();for (MenuPowerTreeDto mp2 : childrenMenuTypeDto) {MenuPowerTreeVo menuPowerTreeVo1 = new MenuPowerTreeVo();List<MenuPowerTreeDto> oneMenuType = new ArrayList<>();oneMenuType.add(mp2);this.buildeTree(oneMenuType, menuPowerTrees, menuPowerTreeVo1);childrenMenuType.add(menuPowerTreeVo1);}Collections.sort(childrenMenuType, Comparator.comparing(MenuPowerTreeVo::getMenuSort));menuPowerTreeVo.setChildrenMenuType(childrenMenuType);}}}public List<String> queryMenuPower(Integer sourceType, List<Integer> roleIds) {List<String> powerList = new ArrayList<>();if (Objects.nonNull(sourceType) && CollectionUtil.isNotEmpty(roleIds)) {List<MenuPowerTreeDto> menuPowerTrees = this.baseMapper.menuPowerTree(sourceType, roleIds);if (CollectionUtil.isNotEmpty(menuPowerTrees)) {List<MenuPowerTreeDto> hasPower = menuPowerTrees.stream().filter(e -> Objects.nonNull(e.getP()) && e.getP() > 0).collect(Collectors.toList());if (CollectionUtil.isNotEmpty(hasPower)) {for (MenuPowerTreeDto p : hasPower) {powerList.add(p.getName());}}}}log.info("queryMenuPower.sourceType:{}.roleIds:{}.powerList:{}", sourceType, JSON.toJSONString(roleIds), JSON.toJSONString(powerList));return powerList;}}
实现效果:
{"code": "000000","msg": "success","timestamp": "2024-04-07 17:38:35","data": {"id": -1,"parentId": -1,"type": null,"name": "父节点","remarks": null,"sourceType": null,"menuSort": 0,"menuLevel": null,"hasPower": null,"childrenMenuType": [{"id": 3,"parentId": 0,"type": 1,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 1,"menuSort": 1,"menuLevel": 1,"hasPower": true,"childrenMenuType": [{"id": 4,"parentId": 3,"type": 1,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 1,"menuSort": 1,"menuLevel": 2,"hasPower": true,"childrenMenuType": null,"childrenButtonType": Array[2]},{"id": 5,"parentId": 3,"type": 1,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 1,"menuSort": 2,"menuLevel": 2,"hasPower": true,"childrenMenuType": null,"childrenButtonType": Array[1]},{"id": 6,"parentId": 3,"type": 1,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 1,"menuSort": 3,"menuLevel": 2,"hasPower": null,"childrenMenuType": null,"childrenButtonType": Array[9]},{"id": 7,"parentId": 3,"type": 1,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 1,"menuSort": 4,"menuLevel": 2,"hasPower": null,"childrenMenuType": null,"childrenButtonType": null},{"id": 22,"parentId": 3,"type": 1,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 1,"menuSort": 5,"menuLevel": 2,"hasPower": null,"childrenMenuType": null,"childrenButtonType": Array[3]},{"id": 23,"parentId": 3,"type": 1,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 1,"menuSort": 6,"menuLevel": 2,"hasPower": null,"childrenMenuType": null,"childrenButtonType": Array[1]}],"childrenButtonType": Array[2]}],"childrenButtonType": null}}=========================================={"code": "000000","msg": "success","timestamp": "2024-04-07 17:39:19","data": {"id": -1,"parentId": -1,"type": null,"name": "父节点","remarks": null,"sourceType": null,"menuSort": 0,"menuLevel": null,"hasPower": null,"childrenMenuType": [{"id": 28,"parentId": 0,"type": 1,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 2,"menuSort": 1,"menuLevel": null,"hasPower": true,"childrenMenuType": null,"childrenButtonType": null},{"id": 29,"parentId": 0,"type": 1,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 2,"menuSort": 2,"menuLevel": null,"hasPower": true,"childrenMenuType": null,"childrenButtonType": [{"id": 30,"parentId": 29,"type": 2,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 2,"menuSort": 0,"menuLevel": null,"hasPower": null,"childrenMenuType": null,"childrenButtonType": null},{"id": 31,"parentId": 29,"type": 2,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 2,"menuSort": 0,"menuLevel": null,"hasPower": null,"childrenMenuType": null,"childrenButtonType": null}]},{"id": 32,"parentId": 0,"type": 1,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 2,"menuSort": 3,"menuLevel": null,"hasPower": null,"childrenMenuType": null,"childrenButtonType": null},{"id": 33,"parentId": 0,"type": 1,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 2,"menuSort": 4,"menuLevel": null,"hasPower": null,"childrenMenuType": null,"childrenButtonType": [{"id": 35,"parentId": 33,"type": 2,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 2,"menuSort": 0,"menuLevel": null,"hasPower": null,"childrenMenuType": null,"childrenButtonType": null}]},{"id": 34,"parentId": 0,"type": 1,"name": "xxx.xxx","remarks": "xxx.xxx","sourceType": 2,"menuSort": 5,"menuLevel": null,"hasPower": null,"childrenMenuType": null,"childrenButtonType": null}],"childrenButtonType": null}}
package xxx.controller;import cn.dev33.satoken.stp.SaLoginConfig;import cn.dev33.satoken.stp.SaLoginModel;import cn.dev33.satoken.stp.SaTokenInfo;import cn.dev33.satoken.stp.StpUtil;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;import java.util.Objects;public class AdminController {//TODO 系统登录和登出//TODO 改为POST请求//登录public RestResponse login(String account, String pwdCipherText, Integer isRememberMe) {//TODO 可以加上登录失败次数校验,错误次数存redis中//TODO 密码加密,这里使用MD5加密,后台分配管理员设置账号时需要存储明文和加密密文,这里取的是加密密文对比//TODO 或者可以加入一个生成验证码验证,提供一个获取验证码的接口给前端,生成后输入验证码验证登录,可以防止接口被刷的风险if (StringUtils.isBlank(account) || StringUtils.isBlank(pwdCipherText)) {return RestResponse.fail("登录账号或密码不为空!");}try {//TODO 根据account、Md5加密密码pwdCipherText查询User/adminUser user = xxxxif (Objects.isNull(user)) {return RestResponse.fail("登录账号不存在!");}if ((StringUtils.isNotBlank(user.getAccount())&& !user.getAccount().equals(account))|| (StringUtils.isNotBlank(user.getCipherText()) && !user.getCipherText().equals(pwdCipherText))) {return RestResponse.fail("登录账号或密码不为正确!");}// 记住我--->`SaLoginModel`为登录参数Model,其有诸多参数决定登录时的各种逻辑SaLoginModel saLoginModel = SaLoginConfig.setDevice("PC") // 此次登录的客户端设备类型, 用于[同端互斥登录]时指定此次登录的设备类型.setIsLastingCookie(true) // 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在).setIsWriteHeader(false); // 是否在登录后将 Token 写入到响应头if (Objects.nonNull(isRememberMe) && isRememberMe.intValue() == 1) {// 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的 timeout 值),全局的timeout设置的是1天,记住我设置的是7天saLoginModel.setTimeout(60 * 60 * 24 * 7);}//加入权限和角色List<String> roleList = StpUtil.getRoleList(admin.getId());saLoginModel.setExtra("roles", roleList);List<String> permissionList = StpUtil.getPermissionList(admin.getId());saLoginModel.setExtra("permissions", permissionList);//这里的id是admin的id主键StpUtil.login(admin.getId(), saLoginModel);SaTokenInfo tokenInfo = StpUtil.getTokenInfo();return RestResponse.success(tokenInfo);} catch (Exception e) {log.error("企业会员外部系统登录异常:{}", e.getMessage());if (StringUtils.isBlank(e.getMessage())) {return RestResponse.fail("登录失败");}return RestResponse.fail("登录失败:{}", e.getMessage());}}// 查询登录状态 请求头带上login的satoken的值 Bearer XXXXXXXpublic RestResponse isLogin() {return RestResponse.success("是否登录:" + StpUtil.isLogin());}//TODO 改为POST请求//注销 请求头带上login的satoken的值 Bearer XXXXXXXpublic RestResponse logout() {StpUtil.logout();return RestResponse.success();}}
https://sa-token.cc/v/v1.36.0/doc.html#/plugin/api-sign
https://www.justauth.cn/

https://sa-token.cc/v/v1.36.0/doc.html#/fun/curr-domain
https://sa-token.cc/v/v1.36.0/doc.html#/more/common-questions
原文作者:Java大飞哥;如有侵权,请联系管理员删除。