轻量级 Java 权限认证框架——Sa-Token

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

Sa-Token 介绍

Sa-Token 是一个轻量级 Java 权限认证框架主要解决登录认证权限认证单点登录OAuth2.0分布式Session会话微服务网关鉴权 等一系列权限相关问题。

Sa-Token最新开发文档地址https://sa-token.cc

Sa-Token功能结构图

在这里插入图片描述

SpringBoot 集成 Sa-Token

  1. 创建 SpringBoot 项目

  2. Pom.xml 文件中添加 Sa-Token 依赖

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

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

  3. 设置配置文件

    支持零配置启动项目 但同时也可以在 application.yml 中增加如下配置。

    server:
        # 端口
        port: 8081
    
    ############## 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
    

Sa-Token 功能

登录认证

  1. 会话登录

    // 会话登录参数填写要登录的账号id建议的数据类型long | int | String 不可以传入复杂类型如User、Admin 等等
    StpUtil.login(Object id);   
    

    StpUtil.login()函数让 Sa-Token 为这个账号创建了一个Token凭证且通过 Cookie 上下文返回给了前端除此之外Sa-Token 在背后做了大量的工作包括但不限于

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

    在 Cookie 功能的加持下我们可以仅靠 StpUtil.login(id) 一句代码就完成登录认证。

    • Cookie 可以从后端控制往浏览器中写入 Token 值。
    • Cookie 会在前端每次发起请求时自动提交 Token 值。
  2. 注销登录

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

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

    // 检验当前会话是否已经登录, 如果未登录则抛出异常`NotLoginException`代表当前会话暂未登录可能的原因有很多
    StpUtil.checkLogin();
    

会话查询

// 获取当前会话账号id, 如果未登录则抛出异常`NotLoginException`
StpUtil.getLoginId();

// 类似查询API还有
StpUtil.getLoginIdAsString();    // 获取当前会话账号id, 并转化为`String`类型
StpUtil.getLoginIdAsInt();       // 获取当前会话账号id, 并转化为`int`类型
StpUtil.getLoginIdAsLong();      // 获取当前会话账号id, 并转化为`long`类型

// ---------- 指定未登录情形下返回的默认值 ----------

// 获取当前会话账号id, 如果未登录则返回null 
StpUtil.getLoginIdDefaultNull();

// 获取当前会话账号id, 如果未登录则返回默认值 `defaultValue`可以为任意类型
StpUtil.getLoginId(T defaultValue);

Token 查询

// 获取当前会话的token值
StpUtil.getTokenValue();

// 获取当前`StpLogic`的token名称
StpUtil.getTokenName();

// 获取指定token对应的账号id如果未登录则返回 null
StpUtil.getLoginIdByToken(String tokenValue);

// 获取当前会话剩余有效期单位s返回-1代表永久有效
StpUtil.getTokenTimeout();

// 获取当前会话的token信息参数
StpUtil.getTokenInfo();

权限认证

因为每个项目的需求不同其权限设计也千变万化 所以 Sa-Token 将 [ 获取当前账号权限码集合 ] 操作以接口的方式暴露给你以方便你根据自己的业务逻辑进行重写。

新建一个类实现 StpInterface 接口例如以下代码

/**
 * 自定义权限验证接口扩展
 */
@Component    // 保证此类被SpringBoot扫描完成Sa-Token的自定义权限验证扩展 
public class StpInterfaceImpl implements StpInterface {

    /**
     * 返回一个账号所拥有的权限码集合 
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // 本list仅做模拟实际项目中要根据具体业务逻辑来查询权限
        List<String> list = new ArrayList<String>();    
        list.add("user.add");
        list.add("user.update");
        list.add("user.get");
        // list.add("user.delete");
        list.add("art.*");
        return list;
    }

    /**
     * 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        // 本list仅做模拟实际项目中要根据具体业务逻辑来查询角色
        List<String> list = new ArrayList<String>();    
        list.add("admin");
        list.add("super-admin");
        return list;
    }

}

参数解释

  • loginId账号id即你在调用 StpUtil.login(id) 时写入的标识值。
  • loginType账号体系标识此处可以暂时忽略在 [ 多账户认证 ] 章节下会对这个概念做详细的解释。

权限校验

// 获取当前账号所拥有的权限集合
StpUtil.getPermissionList();

// 判断当前账号是否含有指定权限, 返回 true 或 false
StpUtil.hasPermission("user.add");        

// 校验当前账号是否含有指定权限, 如果验证未通过则抛出异常: NotPermissionException 
StpUtil.checkPermission("user.add");        

// 校验当前账号是否含有指定权限 [指定多个必须全部验证通过]
StpUtil.checkPermissionAnd("user.add", "user.delete", "user.get");        

// 校验当前账号是否含有指定权限 [指定多个只要其一验证通过即可]
StpUtil.checkPermissionOr("user.add", "user.delete", "user.get");    

角色校验

// 获取当前账号所拥有的角色集合
StpUtil.getRoleList();

// 判断当前账号是否拥有指定角色, 返回 true 或 false
StpUtil.hasRole("super-admin");        

// 校验当前账号是否含有指定角色标识, 如果验证未通过则抛出异常: NotRoleException
StpUtil.checkRole("super-admin");        

// 校验当前账号是否含有指定角色标识 [指定多个必须全部验证通过]
StpUtil.checkRoleAnd("super-admin", "shop-admin");        

// 校验当前账号是否含有指定角色标识 [指定多个只要其一验证通过即可] 
StpUtil.checkRoleOr("super-admin", "shop-admin");        

权限通配符“*”Sa-Token允许你根据通配符指定泛权限例如当一个账号拥有art.*的权限时art.addart.deleteart.update都将匹配通过

注解鉴权

  • @SaCheckLogin: 登录校验 —— 只有登录之后才能进入该方法。
  • @SaCheckRole("admin"): 角色校验 —— 必须具有指定角色标识才能进入该方法。
  • @SaCheckPermission("user:add"): 权限校验 —— 必须具有指定权限才能进入该方法。
  • @SaCheckSafe: 二级认证校验 —— 必须二级认证之后才能进入该方法。
  • @SaCheckBasic: HttpBasic校验 —— 只有通过 Basic 认证后才能进入该方法。
  • @SaIgnore忽略校验 —— 表示被修饰的方法或类无需进行注解鉴权和路由拦截器鉴权。
  • @SaCheckDisable("comment")账号服务封禁校验 —— 校验当前账号指定服务是否被封禁。

Sa-Token 使用全局拦截器完成注解鉴权功能为了不为项目带来不必要的性能负担拦截器默认处于关闭状态
因此为了使用注解鉴权你必须手动将 Sa-Token 的全局拦截器注册到你项目中

注册 Sa-Token 拦截器

SpringBoot2.0为例新建配置类SaTokenConfigure.java

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    // 注册 Sa-Token 拦截器打开注解式鉴权功能 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器打开注解式鉴权功能 
        registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");    
    }
}

保证此类被springboot启动类扫描到即可

关闭注解校验

SaInterceptor 只要注册到项目中默认就会打开注解校验如果要关闭此能力需要

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(
        new SaInterceptor(handle -> {
            SaRouter.match("/**").check(r -> StpUtil.checkLogin());
        }).isAnnotation(false)  // 指定关闭掉注解鉴权能力这样框架就只会做路由拦截校验了 
    ).addPathPatterns("/**");
}

路由拦截鉴权

假设我们有如下需求

项目中所有接口均需要登录认证只有 “登录接口” 本身对外开放

那么给每个接口加上鉴权注解手写全局拦截器似乎都不是非常方便。

注册 Sa-Token 路由拦截器

SpringBoot2.0为例, 新建配置类SaTokenConfigure.java

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    // 注册拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器校验规则为 StpUtil.checkLogin() 登录校验。
        registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
                .addPathPatterns("/**")
                .excludePathPatterns("/user/doLogin"); 
    }
}

以上代码我们注册了一个基于 StpUtil.checkLogin() 的登录校验拦截器并且排除了/user/doLogin接口用来开放登录除了/user/doLogin以外的所有接口都需要登录才能访问。

[记住我] 模式

Sa-Token的登录授权默认就是[记住我]模式为了实现[非记住我]模式你需要在登录时如下设置 StpUtil.login() 函数的第二个参数

// 设置登录账号id为10001第二个参数指定是否为[记住我]当此值为false后关闭浏览器后再次打开需要重新登录
StpUtil.login(10001, false);
  • 勾选 [记住我] 按钮时调用StpUtil.login(10001, true)此时用户即使重启浏览器 Token 依然有效。
  • 不勾选 [记住我] 按钮时调用StpUtil.login(10001, false)此时用户在重启浏览器后 Token 便会消失导致会话失效。

密码加密

Sa-Token 支持摘要加密、对称加密、非对称加密、Base64加密等方式。

// md5加密 
SaSecureUtil.md5("123456");

// sha1加密 
SaSecureUtil.sha1("123456");

// sha256加密 
SaSecureUtil.sha256("123456");

Sa-Token 集成 Redis

方式1、使用 jdk 默认序列化方式

<!-- Sa-Token 整合 Redis 使用 jdk 默认序列化方式 -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-dao-redis</artifactId>
    <version>1.34.0</version>
</dependency>

方式2、使用 jackson 序列化方式

<!-- Sa-Token 整合 Redis 使用 jackson 序列化方式 -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-dao-redis-jackson</artifactId>
    <version>1.34.0</version>
</dependency>

集成 Redis 注意

1. 无论使用哪种序列化方式你都必须为项目提供一个 Redis 实例化方案例如

<!-- 提供Redis连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

2. 引入了依赖我还需要为 Redis 配置连接信息吗
需要只有项目初始化了正确的 Redis 实例Sa-Token才可以使用 Redis 进行数据持久化参考以下yml配置

spring: 
    # redis配置 
    redis:
        # Redis数据库索引默认为0
        database: 1
        # 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

3. 集成 Redis 后是我额外手动保存数据还是框架自动保存
框架自动保存。集成 Redis 只需要引入对应的 pom依赖 即可框架所有上层 API 保持不变。

4. 集成包版本问题
Sa-Token-Redis 集成包的版本尽量与 Sa-Token-Starter 集成包的版本一致否则可能出现兼容性问题。

集成 Thymeleaf

可以在 Thymeleaf 页面中使用 Sa-Token 相关API俗称 —— 标签方言。

1、引入依赖

首先我们确保项目已经引入 Thymeleaf 依赖然后在此基础上继续添加

<!-- 在 thymeleaf 标签中使用 Sa-Token -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-dialect-thymeleaf</artifactId>
    <version>1.34.0</version>
</dependency>

2、注册标签方言对象

在 SaTokenConfigure 配置类中注册 Bean

@Configuration
public class SaTokenConfigure {
    // Sa-Token 标签方言 (Thymeleaf版)
    @Bean
    public SaTokenDialect getSaTokenDialect() {
        return new SaTokenDialect();
    }
}

3、使用标签方言

然后我们就可以愉快的使用在 Thymeleaf 页面中使用标签方言了

登录判断

<h2>标签方言测试页面</h2>
<p>
    登录之后才能显示
    <span sa:login>value</span>
</p>
<p>
    不登录才能显示
    <span sa:notLogin>value</span>
</p>

3.2、角色判断

<p>
    具有角色 admin 才能显示
    <span sa:hasRole="admin">value</span>
</p>
<p>
    同时具备多个角色才能显示
    <span sa:hasRoleAnd="admin, ceo, cto">value</span>
</p>
<p>
    只要具有其中一个角色就能显示
    <span sa:hasRoleOr="admin, ceo, cto">value</span>
</p>
<p>
    不具有角色 admin 才能显示
    <span sa:lackRole="admin">value</span>
</p>复制到剪贴板错误复制成功12345678910111213141516

权限判断

<p>
    具有权限 user-add 才能显示
    <span sa:hasPermission="user-add">value</span>
</p>
<p>
    同时具备多个权限才能显示
    <span sa:hasPermissionAnd="user-add, user-delete, user-get">value</span>
</p>
<p>
    只要具有其中一个权限就能显示
    <span sa:hasPermissionOr="user-add, user-delete, user-get">value</span>
</p>
<p>
    不具有权限 user-add 才能显示
    <span sa:lackPermission="user-add">value</span>
</p>复制到剪贴板错误复制成功12345678910111213141516

调用 Sa-Token 相关API

以上的标签方言可以满足我们大多数场景下的权限判断然后有时候我们依然需要更加灵活的在页面中调用 Sa-Token 框架API

首先在 SaTokenConfigure 配置类中为 Thymeleaf 配置全局对象

public class SaTokenConfigure{
    // ... 其它代码

    // 为 Thymeleaf 注入全局变量以便在页面中调用 Sa-Token 的方法 
    @Autowired
    private void configureThymeleafStaticVars(ThymeleafViewResolver viewResolver) {
        viewResolver.addStaticVariable("stp", StpUtil.stpLogic);
    }
}复制到剪贴板错误复制成功123456789

然后就可以在页面上调用 StpLogic 的 API 了例如

<p>调用 StpLogic 方法调用测试</p>
<p th:if="${stp.isLogin()}">
    从SaSession中取值
    <span th:text="${stp.getSession().get('name')}"></span>
</p>
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java