Sa-Token介绍

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

相关链接

官网:https://sa-token.cc/

Github地址:https://github.com/dromara/sa-token

框架应用原理

接入权限框架

sa-token Maven依赖

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

添加配置文件

具体配置文件含义请前往:https://sa-token.cc/doc.html#/use/config?id=%e6%89%80%e6%9c%89%e5%8f%af%e9%85%8d%e7%bd%ae%e9%a1%b9

sa-token.token-name=satoken
sa-token.timeout=2592000
sa-token.activity-timeout=-1
sa-token.is-concurrent=true
sa-token.is-share=true
sa-token.token-style=uuid
sa-token.is-log=false

配置全局异常捕获,以便于处理非认证用户的提示

@ControllerAdvice // 如果需要关闭全局异常拦截,直接注释此注解即可
public class GlobalExceptionHandler {
    /**
     * Sa-Token没有登陆异常
     *
     * @param req 请求:可以记录一些穿参的内容,这里没用到。
     * @return 返回一个自定义字符串
     */
    @ExceptionHandler({NotLoginException.class})
    @ResponseBody
    @ResponseStatus(value = HttpStatus.UNAUTHORIZED) // 错误码:401
    public String NotLoginException(HttpServletRequest req, NotLoginException e) {
        logger.error("【Sa-Token】:没有登陆异常,{}", e.getMessage());
        return "宝贝,你当前没有登陆哦,请登录后重试!";
    }

    /**
     * Sa-Token账号某功能被封禁
     *
     * @param req 请求:可以记录一些穿参的内容,这里没用到。
     * @return 返回一个自定义字符串
     */
    @ExceptionHandler({DisableServiceException.class})
    @ResponseBody
    @ResponseStatus(value = HttpStatus.UNAUTHORIZED) // 错误码:401
    public String NotLoginException(HttpServletRequest req, DisableServiceException e) {
        logger.error("【Sa-Token】:账号某功能被封禁,{}", e.getMessage());
        return "宝贝,你当前账号此功能被封禁!";
    }

    /**
     * Sa-Token没有权限
     *
     * @param req 请求:可以记录一些穿参的内容,这里没用到。
     * @return 返回一个自定义字符串
     */
    @ExceptionHandler({NotPermissionException.class})
    @ResponseBody
    @ResponseStatus(value = HttpStatus.UNAUTHORIZED) // 错误码:401
    public String NotLoginException(HttpServletRequest req, NotPermissionException e) {
        logger.error("【Sa-Token】:账号没有此权限,{}", e.getMessage());
        return "宝贝,你当前账号没有此权限的能力!";
    }
}

开启Sa-Token注解鉴权

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

添加SaToken的事件监听器!

import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.SaLoginModel;
import org.springframework.stereotype.Component;

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

    /** 每次登录时触发 */
    @Override
    public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
        System.out.println("---------- 自定义侦听器实现 doLogin");
    }

    /** 每次注销时触发 */
    @Override
    public void doLogout(String loginType, Object loginId, String tokenValue) {
        System.out.println("---------- 自定义侦听器实现 doLogout");
    }

    /** 每次被踢下线时触发 */
    @Override
    public void doKickout(String loginType, Object loginId, String tokenValue) {
        System.out.println("---------- 自定义侦听器实现 doKickout");
    }

    /** 每次被顶下线时触发 */
    @Override
    public void doReplaced(String loginType, Object loginId, String tokenValue) {
        System.out.println("---------- 自定义侦听器实现 doReplaced");
    }

    /** 每次被封禁时触发 */
    @Override
    public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {
        System.out.println("---------- 自定义侦听器实现 doDisable");
    }

    /** 每次被解封时触发 */
    @Override
    public void doUntieDisable(String loginType, Object loginId, String service) {
        System.out.println("---------- 自定义侦听器实现 doUntieDisable");
    }

    /** 每次二级认证时触发 */
    @Override
    public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {
        System.out.println("---------- 自定义侦听器实现 doOpenSafe");
    }

    /** 每次退出二级认证时触发 */
    @Override
    public void doCloseSafe(String loginType, String tokenValue, String service) {
        System.out.println("---------- 自定义侦听器实现 doCloseSafe");
    }

    /** 每次创建Session时触发 */
    @Override
    public void doCreateSession(String id) {
        System.out.println("---------- 自定义侦听器实现 doCreateSession");
    }

    /** 每次注销Session时触发 */
    @Override
    public void doLogoutSession(String id) {
        System.out.println("---------- 自定义侦听器实现 doLogoutSession");
    }

    /** 每次Token续期时触发 */
    @Override
    public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
        System.out.println("---------- 自定义侦听器实现 doRenewTimeout");
    }
}

添加角色认证、授权

import cn.dev33.satoken.stp.StpInterface;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

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

/**
 * 自定义权限验证,对用户进行授权、认证
 */
@Slf4j
@Component    // 保证此类被SpringBoot扫描,完成Sa-Token的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {


    /**
     * 给用户授权
     * @param loginId  账号id
     * @param loginType 账号类型
     * @return
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // todo 假装从数据查询loginId的权限列表有 book:read、book:write、eat.*
        List<String> list = new ArrayList<String>();
        list.add("book.read");
        list.add("book.write");
        list.add("eat.*");
        log.info("对用户{},进行授权",loginId);
        return list;
    }

    /**
     * 给用户认证角色,没有角色就不写呗
     * @param loginId  账号id
     * @param loginType 账号类型
     * @return
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        // todo 假装从数据查询loginId的认证列表有 baoan、siji
        List<String> list = new ArrayList<String>();
        list.add("baoan");
        list.add("siji");
        log.info("对用户{},进行认证",loginId);
        return list;
    }
}

接口使用Sa-Token 限制Demo

import cn.dev33.satoken.annotation.SaCheckDisable;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SaCheckLogin // 开启所有登陆鉴权:下面接口必须登陆后才能访问
@RestController
@RequestMapping("/satoken")
public class SaTokenTestController {

    /**
     * 登陆
     * @return
     */
    @SaIgnore // 登陆接口肯定不能受到鉴权限制
    @GetMapping("/login")
    public String login() {
        String account = "1001";
        if (StpUtil.isDisable(account)) {
            long disableTime = StpUtil.getDisableTime(account);
            return "账号当前已封停,请等待解封!剩余解封时间:" + disableTime;
        }
        // 如果设置的是同端互斥,则需要配置文件中,将 isConcurrent 配置为false。然后调用login传入设备信息即可。
        StpUtil.login(account, 60);
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        return "登陆成功!tokenInfo:" + tokenInfo;
    }

    /**
     * 退出登陆
     * @return
     */
    @GetMapping("/logout")
    public String logout() {
        StpUtil.logout("1001");
        return "退出成功!";
    }

    /**
     * 踢号下线
     * @return
     */
    @GetMapping("/kickout")
    public String kickout() {
        StpUtil.kickout("1001");
        return "踢人成功!";
    }

    /**
     * 顶号
     * @return
     */
    @GetMapping("/replaced")
    public String replaced() {
        // todo 这里我是想判断某个设备是否登陆的,没找到API.
        String account = "1001";
        StpUtil.replaced(account, "必须指定设备信息!");
        return "顶号成功!";
    }

    /**
     * 封停。
     * 1、可对账号封停
     * 2、可对账号部分功能封停。
     * 3、还可以根据封停次数,对账号进行封禁时间增长。具体前往官网查看
     *
     * @return
     */
    @SaIgnore
    @GetMapping("/jail")
    public String jail() {
        String account = "1001";
        if (StpUtil.isDisable(account)) {
            long disableTime = StpUtil.getDisableTime(account);
            return "操作失败,账号已经封停过了!剩余解封时间" + disableTime;
        }
        StpUtil.logout(account);
        StpUtil.disable(account, 10); // 注意封停不会强制退出,建议封停后,手动退出
        return "账号封停10秒成功!";
    }

    /**
     * 给账号解封,这里我做测试。添加忽略校鉴权注解。
     * @return
     */
    @SaIgnore
    @SaCheckDisable("release")
    @GetMapping("/release")
    public String jailTest() {
        StpUtil.untieDisable("1001");
        return "账号解封成功";
    }

    /**
     * 用户只要满足有权限且功能没被封禁就能访问
     * @return
     */
    @GetMapping("/sayhello")
    @SaCheckDisable({"hello", "go"}) // 如果账号限制了hello、go功能,即便账号是超管,也无法访问!
    @SaCheckPermission(value = {"book.read"}, orRole = {"baoan"}) // 指定接口权限 book.read或是baoan的Role就可以访问
    public String sayHello() {
        return "此接口可以正常访问!";
    }

    /**
     * 此方法无法访问,没有任何权限、与角色可访问。
     * @return
     */
    @GetMapping("/notallow")
    @SaCheckPermission(value = {"notallow"}, orRole = {"notallow"})
    public String notallow() {
        return "此接口可以正常访问!";
    }
}

完结,自己测试吧!

引入Redis持久化信息

持久化方案:框架的鉴权记录默认只记录在内存中,每次配置的鉴权记录重启就消失了,我们引入Redis持久化方案!使得我们鉴权记录持久化!

官方说:Jackson有可能兼容性差,但是方便阅读,同时提供另一种序列化的Redis方案:https://sa-token.cc/doc.html#/up/integ-redis

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

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

Redis配置

spring.redis.host=XXXX
spring.redis.port=XXXX
spring.redis.password=XXXX
spring.redis.database=0
# Redis连接池配置(Spring默认Redis连接池是lettuce,如果使用jedis的连接池还需要引入其他依赖) 依次是:最大连接、最大空闲、最小连接数、连接等待时间
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-wait=100

框架会自动同步的Redis中。无需人工干预!

特殊说明:
上述文章均是作者实际操作后产出。烦请各位,请勿直接盗用!转载记得标注原文链接:www.zanglikun.com
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取全部资料 ❤