SpringBoot集成权限认证框架(Sa-Token)

SpringBoot集成权限认证框架(Sa-Token)

介绍

身份验证又称“验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认。

身份验证的目的是确认当前所声称为某种身份的用户,确实是所声称的用户。在日常生活中,身份验证并不罕见;比如,通过检查对方的证件,我们一般可以确信对方的身份。

在互联网中身份验证极为重要,不论是web端还是移动端、小程序等,在与后台交互的过程中都是需要携带身份信息的,只有通过身份认证后,后台才会执行相关请求。

常见的认证模式

Cookie模式

所谓 Cookie ,本质上是一个特殊的header参数。

image-20220328132007459

Cookie是一种客户端会话技术,将数据保存在客户端。一小段文本信息随着请求和响应,在客户端和服务器端之间来回传递。根据设定的时间来决定该段文本在客户端保存时长的这种工作模式。如果服务器创建cookie后,会以key=value的形式传递到客户端,并保存在客户端。一旦客户端有服务器发回的文本信息,那么当浏览器再次向服务器发起请求时,也会以key=value这样的形式将文本信息发送到服务器端。

Cookie是一段不超过4KB的小型文本数据,由一个名称(Name)、一个值(Value)和其它几个用于控制Cookie有效期、安全性、使用范围的可选属性组成。如下图所示:

image-20220328132434125

Cookie作用
  • 保持客户端和服务器之间的状态
  • 客户端存储用户的认证信息
  • 客户端存储简单数据
Cookie特点

常规PC端鉴权方法,一般由Cookie模式完成,而 Cookie 有两个特性:

  1. 可由后端控制写入
  2. 每次请求自动提交

这就使得我们在前端代码中,无需任何特殊操作,就能完成鉴权的全部流程(因为整个流程都是后端控制完成的)。

Cookie安全

cookie是可以被客户端修改的,所有它是有安全问题的,重要的隐私信息不能存储在cookie中,我们要慎用cookie。

注意事项:

  • cookie不能存储用户账号密码
  • cookie需要设置超时时间
  • cookie在账号退出时需要最好主动删除
前后台分离(无Cookie模式)

而在app、小程序等前后台分离场景中,一般是没有 Cookie 这一功能的。

这里我们就需要使用token来进行认证。

  1. 前端进行用户登录后,后台会返回一个token到前端
  2. 前端在进行接口请求时,将token存放到header中,格式为:{tokenName: tokenValue}
  3. 后台接收到token就会开始鉴权判断是否有权力进行该接口请求,无token的请求会被直接拒绝提示未登录

使用token模式进行交互,很容易实现SSO单点登录。

使用sa-token

这里我们使用sa-token框架完成单体SpringBoot项目的权限认证功能,选择前后台分离模式,即使用token进行权限认证。前端可以是VUE、APP和小程序等客户端。

sa-token是一个轻量级 Java 权限认证框架,主要解决:登录认证权限认证Session会话单点登录OAuth2.0微服务网关鉴权 等一系列权限相关问题。简化了我们开发权限管理的业务逻辑。

sa-token

image-20220328135018155

集成步骤

用户管理表

这里我们使用RBAC模型,RBAC 是基于角色的访问控制(Role-Based Access Control )在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。

image-20220328140412559

这里我除了用户、角色、权限还多增加了一个部门

用户表
DROP TABLE IF EXISTS auth_user;
CREATE TABLE auth_user(
    id INT NOT NULL   COMMENT '唯一标识' ,
    created_time timestamp   DEFAULT now() COMMENT '创建时间' ,
    updated_time timestamp    COMMENT '修改时间' ,
    username VARCHAR(255)    COMMENT '用户名' ,
    password VARCHAR(255)    COMMENT '密码' ,
    email VARCHAR(255)    COMMENT '邮箱' ,
    phone INTEGER    COMMENT '手机号' ,
    is_deleted VARCHAR(1)   DEFAULT 0 COMMENT '是否删除;0:未删除,1:已删除' ,
    is_enable VARCHAR(1)   DEFAULT 0 COMMENT '是否启用;0:未启用,1:启用' ,
    PRIMARY KEY (id)
)  COMMENT = '用户信息';
角色表
DROP TABLE IF EXISTS auth_role;
CREATE TABLE auth_role(
    id INT NOT NULL   COMMENT '唯一标识' ,
    created_time DATETIME   DEFAULT now() COMMENT '创建时间' ,
    name VARCHAR(255)    COMMENT '角色名称' ,
    remark VARCHAR(255)    COMMENT '备注' ,
    PRIMARY KEY (id)
)  COMMENT = '角色';
权限表
DROP TABLE IF EXISTS auth_permit;
CREATE TABLE auth_permit(
    id INT NOT NULL   COMMENT '唯一标识' ,
    created_time timestamp   DEFAULT now() COMMENT '创建时间' ,
    name VARCHAR(255)    COMMENT '权限名称' ,
    url VARCHAR(255)    COMMENT '授权路径' ,
    remark VARCHAR(255)    COMMENT '备注' ,
    PRIMARY KEY (id)
)  COMMENT = '权限';
部门表
DROP TABLE IF EXISTS auth_org;
CREATE TABLE auth_org(
    id INT NOT NULL   COMMENT '唯一标识' ,
    created_time timestamp   DEFAULT now() COMMENT '创建时间' ,
    name VARCHAR(255)    COMMENT '部门名称' ,
    remark VARCHAR(255)    COMMENT '备注' ,
    PRIMARY KEY (id)
)  COMMENT = '部门机构';
用户角色表
DROP TABLE IF EXISTS auth_user_role;
CREATE TABLE auth_user_role(
    id INT NOT NULL   COMMENT '唯一标识' ,
    created_time timestamp   DEFAULT now() COMMENT '创建时间' ,
    user_id INT    COMMENT '用户id' ,
    role_id INT    COMMENT '角色id' ,
    PRIMARY KEY (id)
)  COMMENT = '用户角色表';
角色权限表
DROP TABLE IF EXISTS auth_role_permit;
CREATE TABLE auth_role_permit(
    id INT NOT NULL   COMMENT '唯一标识' ,
    created_time timestamp   DEFAULT now() COMMENT '创建时间' ,
    role_id INT    COMMENT '角色ID' ,
    permit_id INT    COMMENT '权限ID' ,
    PRIMARY KEY (id)
)  COMMENT = '角色权限表';
部门用户表
DROP TABLE IF EXISTS auth_org_user;
CREATE TABLE auth_org_user(
    id INT NOT NULL   COMMENT '唯一标识' ,
    created_time timestamp   DEFAULT now() COMMENT '创建时间' ,
    org_id INT    COMMENT '部门id' ,
    user_id INT    COMMENT '用户id' ,
    PRIMARY KEY (id)
)  COMMENT = '部门用户表';

以上7张表基本涵盖了常用的RBAC模式,如果需要可以根据具体的业务进行扩展

项目配置

我这里使用的是SpringBoot项目,数据库是PostgreSQL(MySQL也可以),使用mybatis plus框架,数据库连接池为Druid。

默认各位已经熟悉SpringBoot项目的使用,这里只列出了关键配置,像实体类、controller层等都需自己去新建。

项目结构如图:

image-20220328150608591

添加maven依赖

sa-token依赖

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

完整maven依赖

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--springboot内置Jetty , Tomcat , Undertow , 默认是Tomcat,需要排除Tomcat-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
            <exclusions>
                <exclusion>
                    <artifactId>commons-logging</artifactId>
                    <groupId>commons-logging</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- 阿里巴巴的druid数据源 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.23</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
            <exclusions>
                <!-- 排除默认的 HikariCP 数据源 -->
                <exclusion>
                    <groupId>com.zaxxer</groupId>
                    <artifactId>HikariCP</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>

        <!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot-starter</artifactId>
            <version>1.29.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-crypto</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
application.yml配置

sa-token配置

# Sa-Token配置
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: token
  # token有效期,单位s 默认30天, -1代表永不过期
  timeout: 86400
  # 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

完整配置

server:
  port: 8081
spring:
  datasource:
    url: jdbc:postgresql://${base.config.db.hostname}:${base.config.db.port}/${base.config.db.db}?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: ${base.config.db.username}
    password: ${base.config.db.password}
    driver-class-name: org.postgresql.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      # 配置初始化大小、最小、最大
      initial-size: 5
      minIdle: 10
      max-active: 20
      # 配置获取连接等待超时的时间(单位:毫秒)
      max-wait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      time-between-eviction-runs-millis: 2000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 600000
      max-evictable-idle-time-millis: 900000
      # 用来测试连接是否可用的SQL语句,默认值每种数据库都不相同,这是mysql
      validationQuery: select 1
      # 应用向连接池申请连接,并且testOnBorrow为false时,连接池将会判断连接是否处于空闲状态,如果是,则验证这条连接是否可用
      testWhileIdle: true
      # 如果为true,默认是false,应用向连接池申请连接时,连接池会判断这条连接是否是可用的
      testOnBorrow: false
      # 如果为true(默认false),当应用使用完连接,连接池回收连接的时候会判断该连接是否还可用
      testOnReturn: false
      # 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle
      poolPreparedStatements: true
      # 要启用PSCache,必须配置大于0,当大于0时, poolPreparedStatements自动触发修改为true,
      # 在Druid中,不会存在Oracle下PSCache占用内存过多的问题,
      # 可以把这个数值配置大一些,比如说100
      maxOpenPreparedStatements: 20
      # 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作
      keepAlive: true
      # Spring 监控,利用aop 对指定接口的执行时间,jdbc数进行记录
      aop-patterns: "com.dzzh.big_screen.mapper.*"
      ########### 启用内置过滤器(第一个 stat必须,否则监控不到SQL)##########
      filters: stat,wall,log4j2
      # 自己配置监控统计拦截的filter
      filter:
        # 开启druidDatasource的状态监控
        stat:
          enabled: true
          db-type: postgresql
          # 开启慢sql监控,超过2s 就认为是慢sql,记录到日志中
          log-slow-sql: true
          slow-sql-millis: 2000
        # 日志监控,使用slf4j 进行日志输出
        slf4j:
          enabled: true
          statement-log-error-enabled: true
          statement-create-after-log-enabled: false
          statement-close-after-log-enabled: false
          result-set-open-after-log-enabled: false
          result-set-close-after-log-enabled: false
      ########## 配置WebStatFilter,用于采集web关联监控的数据 ##########
      web-stat-filter:
        enabled: true                   # 启动 StatFilter
        url-pattern: /*                 # 过滤所有url
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" # 排除一些不必要的url
        session-stat-enable: true       # 开启session统计功能
        session-stat-max-count: 1000    # session的最大个数,默认100
      ########## 配置StatViewServlet(监控页面),用于展示Druid的统计信息 ##########
      stat-view-servlet:
        enabled: true                   # 启用StatViewServlet
        url-pattern: /druid/*           # 访问内置监控页面的路径,内置监控页面的首页是/druid/index.html
        reset-enable: false              # 不允许清空统计数据,重新计算
        login-username: root            # 配置监控页面访问密码
        login-password: 123456
        allow: 127.0.0.1           # 允许访问的地址,如果allow没有配置或者为空,则允许所有访问
        deny:                                        # 拒绝访问的地址,deny优先于allow,如果在deny列表中,就算在allow列表中,也会被拒绝


# Sa-Token配置
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: token
  # token有效期,单位s 默认30天, -1代表永不过期
  timeout: 86400
  # 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


base:
  config:
    db:
      hostname: 192.168.1.250
      port: 5432
      db: user
      username: postgres
      password: postgres
简单使用sa-token
登录

controller层

    @Resource
    LoginService loginService;

	@ApiOperation(value = "登录接口", notes = "使用账号密码登录用户")
    @GetMapping("doLogin")
    public ResponseResult doLogin(@RequestParam("username") String username, @RequestParam("password") String password) {
        return loginService.doLogin(username, password);
    }

service层

    /**
     * 使用账号密码登录用户
     *
     * @param username 账户
     * @param password 密码
     * @return ResponseResult
     */
    ResponseResult doLogin(String username, String password);

serviceImpl层

@Service
public class LoginServiceImpl implements LoginService {

    @Resource
    AuthUserMapper authUserMapper;

    @Resource
    BCryptPasswordEncoder bCryptPasswordEncoder;

    @Override
    public ResponseResult doLogin(String username, String password) {
        QueryWrapper<AuthUser> queryUser = new QueryWrapper<>();
        queryUser.eq("username", username);
        AuthUser authUser = authUserMapper.selectOne(queryUser);
        if (authUser == null) {
            return new ResponseResult(ResponseCode.FAILURE,"用户不存在!");
        }
        Integer id = authUser.getId();
        String userPassword = authUser.getPassword();
        if (!"1".equals(authUser.getIsEnable())) {
            return new ResponseResult(ResponseCode.FAILURE, "用户未激活,请联系管理员!");
        }
        if (!bCryptPasswordEncoder.matches(password, userPassword)) {
            return new ResponseResult(ResponseCode.FAILURE, "用户名密码不正确!");
        }
        // sa-token登录
        StpUtil.login(id);
        SaTokenInfo saTokenInfo = StpUtil.getTokenInfo();
        String token = saTokenInfo.getTokenValue();
        long tokenTimeout = saTokenInfo.getTokenTimeout();
        authUser.setPassword("");
        HashMap<Object, Object> res = new HashMap<>();
        res.put("token", token);
        res.put("tokenTimeout", tokenTimeout);
        res.put("info", authUser);
        return new ResponseResult(ResponseCode.SUCCESS, res);
    }
}

返回的结构如图所示:

image-20220328153443486

前台拿到token,就可以存起来了。

到这集成sa-token已经完成50%了,这里实现了认证,后面需要实现授权。

配置sa-token授权

在RBAC模型中不同的用户拥有不同的角色,不同的角色拥有的权限是不一样的。接口拦截需要和权限进行绑定,拥有权限的用户可以访问对应的接口。为了实现这个效果,我们需要自定义实现拦截器。

配置拦截器

继承WebMvcConfigurer实现自定义的拦截方法,设置指定的接口需要指定的权限,

SaTokenConfigure.java

import cn.dev33.satoken.interceptor.SaRouteInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
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) {
        // 注册注解拦截器,并排除不需要注解鉴权的接口地址 (与登录拦截器无关)
        registry.addInterceptor(new SaRouteInterceptor((req, res, handler) -> {

            // 登录认证 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
            SaRouter.match("/**", "/acc/doLogin", r -> StpUtil.checkLogin());
            // 拥有/user/atCheck权限的用户才能访问该接口
            SaRouter.match("/acc/atCheck", r -> StpUtil.checkPermission("/user/atCheck"));

        })).addPathPatterns("/**");
    }
}
配置sa-token自定义权限验证接口扩展

sa-token已经为我们实现了整个认证授权逻辑了,我只需要根据项目的业务需求来实现sa-token自定义权限验证接口扩展就行。新建一个实现类

StpInterfaceImpl.java

import cn.dev33.satoken.stp.StpInterface;

import com.sun.natural_resources.entity.AuthPermit;
import com.sun.natural_resources.entity.AuthRole;
import com.sun.natural_resources.mapper.AuthUserMapper;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 自定义权限验证接口扩展
 *
 * @author Ct
 */
@Component
public class StpInterfaceImpl implements StpInterface {

    @Resource
    AuthUserMapper userMapper;

    /**
     * 返回一个账号所拥有的权限码集合
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        System.out.println();
        List<AuthPermit> permission = userMapper.getPermitByUserId(Integer.valueOf(loginId.toString()));
        System.out.println(permission);
        return permission.stream().map(AuthPermit::getName).collect(Collectors.toList());
    }
}

AuthUserMapper.java

    /**
     * 通过用户id获取用户权限
     *
     * @param id 用户id
     * @return 权限集合
     */
    List<AuthPermit> getPermitByUserId(Integer id);

AuthUserMapper.xml

    <select id="getPermitByUserId" resultType="com.sun.natural_resources.entity.AuthPermit">
        select ap.*
        from auth_user_role aur
                 right join auth_role_permit arp on aur.role_id = arp.role_id
                 right join auth_permit ap on arp.permit_id = ap.id
        where aur.user_id = #{id}
    </select>
配置全局异常

sa-token很贴心的为我们提供了全局异常处理示例

封装返回值

AjaxJson.java

import java.io.Serializable;
import java.util.List;


/**
 * ajax请求返回Json格式数据的封装
 */
public class AjaxJson implements Serializable{
    // 序列化版本号
    private static final long serialVersionUID = 1L;
    // 成功状态码
    public static final int CODE_SUCCESS = 200;
    // 错误状态码
    public static final int CODE_ERROR = 500;
    // 警告状态码
    public static final int CODE_WARNING = 501;
    // 无权限状态码
    public static final int CODE_NOT_JUR = 403;
    // 未登录状态码
    public static final int CODE_NOT_LOGIN = 401;
    // 无效请求状态码
    public static final int CODE_INVALID_REQUEST = 400;
    // 状态码
    public int code;
    // 描述信息
    public String msg;
    // 携带对象
    public Object data;
    // 数据总数,用于分页
    public Long dataCount;

    /**
     * 返回code
     * @return
     */
    public int getCode() {
        return this.code;
    }

    /**
     * 给msg赋值,连缀风格
     */
    public AjaxJson setMsg(String msg) {
        this.msg = msg;
        return this;
    }
    public String getMsg() {
        return this.msg;
    }

    /**
     * 给data赋值,连缀风格
     */
    public AjaxJson setData(Object data) {
        this.data = data;
        return this;
    }

    /**
     * 将data还原为指定类型并返回
     */
    @SuppressWarnings("unchecked")
    public <T> T getData(Class<T> cs) {
        return (T) data;
    }

    // ============================  构建  ==================================

    public AjaxJson(int code, String msg, Object data, Long dataCount) {
        this.code = code;
        this.msg = msg;
        this.data = data;
        this.dataCount = dataCount;
    }

    // 返回成功
    public static AjaxJson getSuccess() {
        return new AjaxJson(CODE_SUCCESS, "ok", null, null);
    }
    public static AjaxJson getSuccess(String msg) {
        return new AjaxJson(CODE_SUCCESS, msg, null, null);
    }
    public static AjaxJson getSuccess(String msg, Object data) {
        return new AjaxJson(CODE_SUCCESS, msg, data, null);
    }
    public static AjaxJson getSuccessData(Object data) {
        return new AjaxJson(CODE_SUCCESS, "ok", data, null);
    }
    public static AjaxJson getSuccessArray(Object... data) {
        return new AjaxJson(CODE_SUCCESS, "ok", data, null);
    }

    // 返回失败
    public static AjaxJson getError() {
        return new AjaxJson(CODE_ERROR, "error", null, null);
    }
    public static AjaxJson getError(String msg) {
        return new AjaxJson(CODE_ERROR, msg, null, null);
    }

    // 返回警告
    public static AjaxJson getWarning() {
        return new AjaxJson(CODE_ERROR, "warning", null, null);
    }
    public static AjaxJson getWarning(String msg) {
        return new AjaxJson(CODE_WARNING, msg, null, null);
    }

    // 返回未登录
    public static AjaxJson getNotLogin() {
        return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
    }

    // 返回没有权限的
    public static AjaxJson getNotJur(String msg) {
        return new AjaxJson(CODE_NOT_JUR, msg, null, null);
    }

    // 返回一个自定义状态码的
    public static AjaxJson get(int code, String msg){
        return new AjaxJson(code, msg, null, null);
    }

    // 返回分页和数据的
    public static AjaxJson getPageData(Long dataCount, Object data){
        return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
    }

    // 返回,根据受影响行数的(大于0=ok,小于0=error)
    public static AjaxJson getByLine(int line){
        if(line > 0){
            return getSuccess("ok", line);
        }
        return getError("error").setData(line);
    }

    // 返回,根据布尔值来确定最终结果的  (true=ok,false=error)
    public static AjaxJson getByBoolean(boolean b){
        return b ? getSuccess("ok") : getError("error");
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @SuppressWarnings("rawtypes")
    @Override
    public String toString() {
        String data_string = null;
        if(data == null){

        } else if(data instanceof List){
            data_string = "List(length=" + ((List)data).size() + ")";
        } else {
            data_string = data.toString();
        }
        return "{"
                + "\"code\": " + this.getCode()
                + ", \"msg\": \"" + this.getMsg() + "\""
                + ", \"data\": " + data_string
                + ", \"dataCount\": " + dataCount
                + "}";
    }

}
全局异常

GlobalException.java

import cn.dev33.satoken.exception.DisableLoginException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 全局异常处理
 */
@ControllerAdvice
public class GlobalException {

    // 全局异常拦截(拦截项目中的所有异常)
    @ResponseBody
    @ExceptionHandler
    public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) {

        // 打印堆栈,以供调试
        System.out.println("全局异常---------------");
//        System.out.println(e.getMessage());

        // 不同异常返回不同状态码
        AjaxJson aj = null;
        // 如果是未登录异常
        if (e instanceof NotLoginException) {
            NotLoginException ee = (NotLoginException) e;
            aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
        }
        // 如果是角色异常
        else if (e instanceof NotRoleException) {
            NotRoleException ee = (NotRoleException) e;
            aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
        }
        // 如果是权限异常
        else if (e instanceof NotPermissionException) {
            NotPermissionException ee = (NotPermissionException) e;
            aj = AjaxJson.getNotJur("无此权限:" + ee.getCode());
        }
        // 如果是被封禁异常
        else if (e instanceof DisableLoginException) {
            DisableLoginException ee = (DisableLoginException) e;
            aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封");
        } else {    // 普通异常, 输出:500 + 异常信息
            aj = AjaxJson.getError(e.getMessage());
        }

        // 返回给前端
        return aj;
    }

}

权限认证测试

无token测试

这里模拟head没有token的情况(用户未登录),如图所示会提醒401

image-20220328163939510

登录成功后无权限测试

这里使用测试账号,角色为测试角色未分配select-user权限

image-20220328164242134

这里提醒为无权限

登录后有权限测试

这里使用管理员账号,角色为管理员,分配了select-user权限

image-20220328170228694

可以看到有返回值了

结语

sa-token真是利器,使用它可以为我们减少不少的开发过程,而且这个是国产开源项目,stars也不少,文档对国人很友好,支持国产开源!

这里我要说一下,部分内容参考我朋友的教程springboot集成 Sa-Token、mybatisplus实现RPAC_ct1104的博客-CSDN博客

后面有时间再说说sa-token在微服务中的使用!

  • 6
    点赞
  • 102
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
### 回答1: MySQL DBA认证题库是为帮助DBA(数据库管理员)准备和通过MySQL DBA认证而设计的题库。这个题库包含了一系列与MySQL DBA相关的知识和技能的问题和答案。 MySQL DBA认证题库的目的是评估DBA在MySQL数据库管理方面的专业水平和能力。它覆盖了MySQL数据库的基础知识、体系结构、安装和配置、备份和恢复、高可用性和故障处理、性能优化等方面的内容。 在准备MySQL DBA认证考试时,使用这个题库可以帮助DBA加深对MySQL数据库管理的理解,并检验自己的知识和技能。通过反复练习题库中的问题,DBA可以更好地掌握如何应对各种MySQL数据库管理问题。 此外,MySQL DBA认证题库还可以帮助DBA了解MySQL DBA认证考试的题型和考试要求。通过研究和理解题库中的问题,DBA可以更好地应对考试中的各种题型,并提前了解考试中可能出现的重点和难点,为自己的备考提供指导。 总之,MySQL DBA认证题库是DBA备考MySQL DBA认证考试的重要资源,它不仅可以帮助DBA巩固和提升自己的MySQL数据库管理技能,还可以为DBA提供备考的方向和指导,帮助DBA成功通过MySQL DBA认证考试。 ### 回答2: MySQL DBA(数据库管理员)认证题库是一套用于帮助DBA学习和准备MySQL DBA认证考试的题目集合。这个题库包含了丰富的MySQL DBA相关的问题,涵盖了各个方面的知识点和技能要求。 MySQL DBA认证题库的目的是评估DBA在MySQL数据库管理方面的专业能力和理解程度。它可以帮助DBA自我评估和强化他们对MySQL数据库的掌握能力。通过不断练习和学习认证题库中的问题,DBA可以深入了解和掌握MySQL数据库的各个方面,包括性能优化、安全管理、备份和恢复等重要内容。 认证题库中的问题包括单选题、多选题和简答题等不同类型的题目。这些问题旨在考察DBA对MySQL数据库管理的全面理解和技能运用能力。回答这些问题需要DBA对MySQL的架构、SQL语法、索引优化、高可用性等方面有一定的了解和经验。 通过使用MySQL DBA认证题库,DBA可以系统地学习和巩固MySQL的相关知识,并对自己在数据库管理方面的能力进行评估。同时,题库中的解析和参考答案也可以帮助DBA了解问题的答案和解决思路,从而更好地准备和应对MySQL DBA认证考试。 总之,MySQL DBA认证题库对于DBA的学习和备考非常有价值,可以帮助DBA熟悉MySQL数据库管理的各个方面,提升自己的技能水平,顺利通过MySQL DBA认证考试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孙霸天

你的打赏是我不断创作的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值