赞
踩
身份验证又称“验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认。
身份验证的目的是确认当前所声称为某种身份的用户,确实是所声称的用户。在日常生活中,身份验证并不罕见;比如,通过检查对方的证件,我们一般可以确信对方的身份。
在互联网中身份验证极为重要,不论是web端还是移动端、小程序等,在与后台交互的过程中都是需要携带身份信息的,只有通过身份认证后,后台才会执行相关请求。
所谓 Cookie ,本质上是一个特殊的header
参数。
Cookie是一种客户端会话技术
,将数据保存在客户端。一小段文本信息随着请求和响应,在客户端和服务器端之间来回传递。根据设定的时间来决定该段文本在客户端保存时长的这种工作模式。如果服务器创建cookie后,会以key=value的形式传递到客户端,并保存在客户端。一旦客户端有服务器发回的文本信息,那么当浏览器再次向服务器发起请求时,也会以key=value这样的形式将文本信息发送到服务器端。
Cookie是一段不超过4KB的小型文本数据,由一个名称(Name)、一个值(Value)和其它几个用于控制Cookie有效期、安全性、使用范围的可选属性组成。如下图所示:
常规PC端鉴权方法,一般由Cookie模式
完成,而 Cookie 有两个特性:
这就使得我们在前端代码中,无需任何特殊操作,就能完成鉴权的全部流程(因为整个流程都是后端控制完成的)。
cookie是可以被客户端修改的,所有它是有安全问题的,重要的隐私信息不能存储在cookie中,我们要慎用cookie。
注意事项:
而在app、小程序等前后台分离场景中,一般是没有 Cookie 这一功能的。
这里我们就需要使用token来进行认证。
{tokenName: tokenValue}
使用token模式进行交互,很容易实现SSO单点登录。
这里我们使用sa-token框架完成单体SpringBoot项目的权限认证功能,选择前后台分离模式,即使用token进行权限认证。前端可以是VUE、APP和小程序等客户端。
sa-token是一个轻量级 Java 权限认证框架,主要解决:登录认证
、权限认证
、Session会话
、单点登录
、OAuth2.0
、微服务网关鉴权
等一系列权限相关问题。简化了我们开发权限管理的业务逻辑。
这里我们使用RBAC模型,RBAC 是基于角色的访问控制(Role-Based Access Control )在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
这里我除了用户、角色、权限还多增加了一个部门
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层等都需自己去新建。
项目结构如图:
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>
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
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);
}
}
返回的结构如图所示:
前台拿到token,就可以存起来了。
到这集成sa-token已经完成50%了,这里实现了认证,后面需要实现授权。
在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自定义权限验证接口扩展就行。新建一个实现类
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;
}
}
这里模拟head没有token的情况(用户未登录),如图所示会提醒401
这里使用测试账号,角色为测试角色未分配select-user权限
这里提醒为无权限
这里使用管理员账号,角色为管理员,分配了select-user权限
可以看到有返回值了
sa-token真是利器,使用它可以为我们减少不少的开发过程,而且这个是国产开源项目,stars也不少,文档对国人很友好,支持国产开源!
这里我要说一下,部分内容参考我朋友的教程springboot集成 Sa-Token、mybatisplus实现RPAC_ct1104的博客-CSDN博客
后面有时间再说说sa-token在微服务中的使用!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。