SpingBoot整合Sa-Token框架(1)

一、文档参考:框架介绍 (sa-token.cc)

框架生态——开源项目 (sa-token.cc)

二、与SpingBoot整合

1、创建项目

        在 IDE 中新建一个 SpringBoot 项目,例如:sa-token-demo-springboot(不会的同学请自行百度或者参考:SpringBoot-Pure

2、添加依赖

        这个是springboot web 项目使用的一个依赖:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>

         引入sa-token依赖:

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

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

3、设置配置文件

        你可以零配置启动项目 ,但同时你也可以在 application.yml 中增加如下配置,定制性使用框架:框架配置 (sa-token.cc)

server:
  # 端口
  port: 8081

############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
  # token 名称(同时也是 cookie 名称)
  token-name: satoken
  # token 有效期(单位:秒) 默认30天,-1 代表永久有效
  timeout: 2592000
  # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
  active-timeout: -1
  # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
  is-share: true
  # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
  token-style: uuid
  # 是否输出操作日志
  is-log: true

4、创建启动类

import cn.dev33.satoken.SaManager;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SaTokenDemoApplication {
    public static void main(String[] args) throws JsonProcessingException {
        SpringApplication.run(SaTokenDemoApplication.class, args);
        System.out.println("启动成功,Sa-Token 配置如下:" + SaManager.getConfig());
    }
}

5、创建Controller类

import cn.dev33.satoken.stp.StpUtil;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user/")
public class UserController {
    
    // 测试登录,浏览器访问: http://localhost:8081/user/doLogin?username=zhang&password=123456
    @RequestMapping("doLogin")
    public String doLogin(String username, String password) {
        // 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
        if("zhang".equals(username) && "123456".equals(password)) {
            StpUtil.login(10001);
            return "登录成功";
        }
        return "登录失败";
    }

    // 查询登录状态,浏览器访问: http://localhost:8081/user/isLogin
    @RequestMapping("isLogin")
    public String isLogin() {
        return "当前会话是否登录:" + StpUtil.isLogin();
    }
}

6、运行测试 

三、基于SpingBoot基础操作

​Sa-Token 目前主要五大功能模块:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权。

1、登录认证 

( 单端登录、多端登录、同端互斥登录、七天内免登录 )

@SpringBootApplication
public class AppLogin {
    public static void main(String[] args) {
        SpringApplication.run(AppLogin.class,args);
    }
}

 启动类代码

package com.satoken.controller;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/user/")
public class UserController {

    // 测试登录,浏览器访问: http://localhost:8081/user/doLogin?username=zhang&password=123456
    @RequestMapping("doLogin")
    public String doLogin(String username, String password) {
        // 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
        if("zhang".equals(username) && "123456".equals(password)) {
            StpUtil.login(10001);
            return "登录成功";
        }
        return "登录失败";
    }

    // 查询登录状态,浏览器访问: http://localhost:8081/user/isLogin
    @RequestMapping("isLogin")
    public String isLogin() {
        return "当前会话是否登录:" + StpUtil.isLogin();
    }

    // 退出 http://localhost:8081/user/logout
    @GetMapping("/logout")
    public boolean logout(){
        StpUtil.logout();
        return true;
    }

     检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`
    // http://localhost:8081/user/checkLogin
    @GetMapping("/checkLogin")
    public SaResult checkLogin(){

        try{
            StpUtil.checkLogin();
            return SaResult.ok("已经登录");
        }catch (NotLoginException e){
            e.printStackTrace();
            return SaResult.error("未登录");
        }
    }

    // 获取登录ID http://localhost:8081/user/getLoginId
    @GetMapping("/getLoginId")
    public Map<String,Object> getLoginId(){
        Map<String,Object> map = new HashMap<>();
        // 获取当前会话账号id, 如果未登录,则抛出异常:`NotLoginException`
        map.put("getLoginId",StpUtil.getLoginId());
        map.put("getLoginIdAsInt",StpUtil.getLoginIdAsInt());// 获取当前会话账号id, 并转化为`String`类型
        return map;
    }

    // token信息 http://localhost:8081/user/getToken
    @GetMapping("/getToken")
    public SaTokenInfo getToken(){
        return StpUtil.getTokenInfo();
    }

}

测试:

2、权限认证 

        所谓权限认证,核心逻辑就是判断一个账号是否拥有指定权限:有,就让你通过。没有?那禁止访问!深入到底层数据中就是每个账号都会拥有一组权限码集合,框架来校验这个集合中是否包含指定的权限码。

        例如:当前账号拥有权限码集合 ["user-add", "user-delete", "user-get"],这时候我来校验权限 "user-update",则其结果就是:验证失败,禁止访问

        所以现在问题的核心就是两个:如何获取一个账号所拥有的权限码集合?本次操作需要验证的权限码是哪个?

        因为每个项目的需求不同,其权限设计也千变万化,因此 [ 获取当前账号权限码集合 ] 这一操作不可能内置到框架中, 所以 Sa-Token 将此操作以接口的方式暴露给你,方便你根据自己的业务逻辑进行重写。你需要做的就是新建一个类,实现 StpInterface接口,例如以下代码:

package com.satoken.service;

import cn.dev33.satoken.stp.StpInterface;
import org.springframework.stereotype.Component;

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

/**
 * 自定义权限加载接口实现类
 */
@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("101");
        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<>();
        list.add("admin");
        list.add("super-admin");
        return list;
    }
}

然后在controller层

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;
import java.util.List;

@RestController
@RequestMapping("/auth")
public class AuthenticationController {

    // 获取所有权限 http://localhost:8081/auth/getPermissionList
    @GetMapping("/getPermissionList")
    public List<String> getPermissionList(){
        return StpUtil.getPermissionList();
    }

    // 权限判断 http://localhost:8081/auth/hasPermission
    @GetMapping("/hasPermission")
    public boolean hasPermission(){
        // 判断:当前账号是否含有指定权限, 返回 true 或 false
        return StpUtil.hasPermission("user.add");
    }

    // 权限检查 http://localhost:8081/auth/checkPermission
    @GetMapping("/checkPermission")
    public boolean checkPermission(){
        // 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
        StpUtil.checkPermission("user.xadd");
        return true;
    }

    // 权限检查 http://localhost:8081/auth/checkPermissionAnd
    @GetMapping("/checkPermissionAnd")
    public boolean checkPermissionAnd(){
        // 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
        StpUtil.checkPermissionAnd("user.add", "user.add", "art.get");
        return true;
    }

    // http://localhost:8081/auth/getRoleList
    @GetMapping("/getRoleList")
    public List<String> getRoleList(){
        // 获取:当前账号所拥有的角色集合
        return StpUtil.getRoleList();
    }

    // http://localhost:8081/auth/hasRole?role=
    @GetMapping("/hasRole")
    public boolean hasRole(String role){
        return StpUtil.hasRole(role);
    }

    // http://localhost:8081/auth/checkRole?role=
    @GetMapping("/checkRole")
    public boolean checkRole(String role){
        StpUtil.checkRole(role);
        return true;
    }
}

获取所有权限(前提是要先登录,相当先要建立session 会话) 

 权限判断(后面的接口同理)

拦截全局异常:

        鉴权失败,抛出异常,要把异常显示给用户看吗?当然不可以!你可以创建一个全局异常拦截器,统一返回给前端的格式,参考:

@RestControllerAdvice
public class GlobalExceptionHandler {
    // 全局异常拦截 
    @ExceptionHandler
    public SaResult handlerException(Exception e) {
        e.printStackTrace(); 
        return SaResult.error(e.getMessage());
    }
}

 

 

如何将权限精确到按钮级(权限范围可以控制到页面上的每一个按钮是否显示

思路:如此精确的范围控制只依赖后端已经难以完成,此时需要前端进行一定的逻辑判断。

如果是前后端一体项目,可以参考:Thymeleaf 标签方言,如果是前后端分离项目,则:

  1. 在登录时,把当前账号拥有的所有权限码一次性返回给前端。
  2. 前端将权限码集合保存在localStorage或其它全局状态管理对象中。
  3. 在需要权限控制的按钮上,使用 js 进行逻辑判断,例如在Vue框架中我们可以使用如下写法:
<button v-if="arr.indexOf('user.delete') > -1">删除按钮</button>

        注意:以上写法只为提供一个参考示例,不同框架有不同写法,大家可根据项目技术栈灵活封装进行调用。

3、踢人下线

所谓踢人下线,核心操作就是找到指定 loginId 对应的 Token,并设置其失效。

强制注销:

StpUtil.logout(10001);                    // 强制指定账号注销下线 
StpUtil.logout(10001, "PC");              // 强制指定账号指定端注销下线 
StpUtil.logoutByTokenValue("token");      // 强制指定 Token 注销下线 

踢人下线:

StpUtil.kickout(10001);                    // 将指定账号踢下线 
StpUtil.kickout(10001, "PC");              // 将指定账号指定端踢下线
StpUtil.kickoutByTokenValue("token");      // 将指定 Token 踢下线

 登录之后,根据 id 踢人下线

 

4、注解鉴权

        尽管使用代码鉴权非常方便,但是我仍希望把鉴权逻辑和业务逻辑分离开来,我可以使用注解鉴权吗?当然可以!注解鉴权 —— 优雅的将鉴权与业务代码分离!但是注解鉴权也相对而言,不方便改变,没有代码鉴权那样可以动态鉴权。

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

        Sa-Token 使用全局拦截器完成注解鉴权功能,为了不为项目带来不必要的性能负担,拦截器默认处于关闭状态。因此,为了使用注解鉴权,你必须手动将 Sa-Token 的全局拦截器注册到你项目中:(以SpringBoot2.0为例,新建配置类SaTokenConfigure.java

import cn.dev33.satoken.interceptor.SaInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

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

controller层:

import cn.dev33.satoken.annotation.*;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/auth")
public class AuthenticationController {
    // 登录校验:只有登录之后才能进入该方法
    // http://localhost:8081/auth/info
    @SaCheckLogin
    @RequestMapping("info")
    public String info() {
        return "查询用户信息";
    }

    // 角色校验:必须具有指定角色才能进入该方法
    // http://localhost:8081/auth/check_role
    @SaCheckRole("super-xadmin")
    @RequestMapping("check_role")
    public String check_role() {
        return "用户增加";
    }

    // 权限校验:必须具有指定权限才能进入该方法
    @SaCheckPermission("user-add")
    @RequestMapping("check_permission")
    public String check_permission() {
        return "用户增加";
    }

    // 校验当前账号是否被封禁 comment 服务,如果已被封禁会抛出异常,无法进入方法
    @SaCheckDisable("comment")
    @RequestMapping("send")
    public String send() {
        return "查询用户信息";
    }

    // 注解式鉴权:只要具有其中一个权限即可通过校验
    @RequestMapping("atJurOr")
    @SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR)
    public SaResult atJurOr() {
        return SaResult.data("用户信息");
    }

    // 角色权限双重 “or校验”:具备指定权限或者指定角色即可通过校验
    @RequestMapping("userAdd")
    @SaCheckPermission(value = "user.add", orRole = "admin")
    public SaResult userAdd() {
        return SaResult.data("用户信息");
    }

    // 此接口加上了 @SaIgnore 可以游客访问
    @SaIgnore
    @RequestMapping("getList")
    public SaResult getList() {
        // ...
        return SaResult.ok();
    }

    // 在 `@SaCheckOr` 中可以指定多个注解,只要当前会话满足其中一个注解即可通过验证,进入方法。
    @SaCheckOr(
            login = @SaCheckLogin,
            role = @SaCheckRole("admin"),
            permission = @SaCheckPermission("user.add")
    )
    @RequestMapping("test")
    public SaResult test() {
        // ...
        return SaResult.ok();
    }

}

        在 注解式鉴权 实现了注解鉴权, 但是默认的拦截器模式却有一个缺点,那就是无法在Controller层以外的代码使用进行校验。因此Sa-Token提供AOP插件,你只需在pom.xml里添加如下依赖,便可以在任意层级使用注解鉴权。

<!-- Sa-Token 整合 SpringAOP 实现注解鉴权 -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-aop</artifactId>
    <version>1.35.0.RC</version>
</dependency>

  • 拦截器模式,只能把注解写在Controller层,AOP模式,可以将注解写在任意层级
  • 拦截器和AOP模式不可同时集成,否则会在Controller层发生一个注解校验两次的bug

猜你喜欢

转载自blog.csdn.net/weixin_49171365/article/details/130634089