SpringBoot集成Sa-Token框架完成登录认证和权限校验

Sa-Token介绍
Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。
Sa-Token 目前主要五大功能模块:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权。
Sa-Token的官方网站: Sa-Token

本章节主要讲解登录认证和权限认证模块,由于我也是个新手,小白,文章内有什么错误或者是可以优化的地方,热烈欢迎给我指点一二,废话不多说,正片开始!
一,创建和配置
1.创建SpringBoot项目
创建项目很简单,在此就不做赘述了,建议使用Mybatis-plus框架,可以根据数据库直接生成三层类
不知道怎么创建项目的可以参考这篇文章: day33-37-SpringBootV12(整合Spring,SpringMVC,Mybatis,日志,api测试等框架)-CSDN博客
只需要看前面一部分创建项目的教程就可以了
2.添加Sa-Token依赖
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot-starter</artifactId><version>1.39.0</version></dependency>
3.设置配置文件
有两种配置文件,风格不一样,选择自己喜欢的,新手的话建议选择.properties风格的
下面配置文件的风格是application.yml风格的
server:# 端口port:8081#配置mysqlspring:datasource:driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sa-token-db?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF8&useUnicode=trueusername: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
druid:remove-abandoned:truetime-between-eviction-runs-millis:60000remove-abandoned-timeout-millis:1800############## 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
下面的配置文件风格是application.properties风格的
# 端口
server.port=8081
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
# token 名称(同时也是 cookie 名称)
sa-token.token-name=satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
sa-token.timeout=2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
sa-token.active-timeout=-1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
sa-token.is-concurrent=true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
sa-token.is-share=true
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
sa-token.token-style=uuid
# 是否输出操作日志
sa-token.is-log=true
4.创建启动类
在项目中新建包,com.xxx,在这个包内创建主类:SaTokenDemoApplication
@SpringBootApplicationpublicclassSaTokenDemoApplication{publicstaticvoidmain(String[] args)throwsJsonProcessingException{SpringApplication.run(SaTokenDemoApplication.class, args);System.out.println("启动成功,Sa-Token 配置如下:"+SaManager.getConfig());}}
创建好后的项目是这样的,此时可以启动一下,看一下有没有报错

启动成功

5.创建数据库文件
CREATE TABLE `users` (
`user_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
`password` varchar(125) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
`user_name` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户姓名',
`permission_level` int(11) NOT NULL DEFAULT 0 COMMENT '权限等级',
`is_deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否被删除',
`create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
`update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
`cellphone_number` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号',
`user_type` int(10) NULL DEFAULT NULL COMMENT '用户类型:1超级管理员,2管理员,3运维人员,4工人,5游客',
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
创建数据库,执行SQL语句

6.引入依赖
引入本次教程所需要的所有依赖,除了上面已经引入过的Sa-Token依赖
<!--mybatis plus 依赖 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.3.1.tmp</version></dependency><!-- 阿里巴巴的Druid数据源依赖启动器 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.22</version></dependency><!-- mysql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.21</version></dependency><!-- mybatis-plus 模板引擎--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.3.1.tmp</version></dependency><!-- 添加 模板引擎 依赖 --><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>2.2</version></dependency><!-- lombok 生成set get --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.10</version></dependency><!--hutool工具包--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.12</version></dependency>
7.创建数据库文件自动生成类
因为我们要使用mybatis-plus框架,而这个框架可以自动根据数据库表生成三层类,所以我们要有一个自动生成代码文件的类
7.1 生成代码类
需要修改部分代码,下面注释有:**TODO:(需要修改)**的地方都要修改成自己的
启动这个类之前需要先启动主类,然后才可以运行这个生成代码类
packagecom.pj;importcom.baomidou.mybatisplus.annotation.IdType;importcom.baomidou.mybatisplus.generator.AutoGenerator;importcom.baomidou.mybatisplus.generator.config.DataSourceConfig;importcom.baomidou.mybatisplus.generator.config.GlobalConfig;importcom.baomidou.mybatisplus.generator.config.PackageConfig;importcom.baomidou.mybatisplus.generator.config.StrategyConfig;importcom.baomidou.mybatisplus.generator.config.rules.DateType;importcom.baomidou.mybatisplus.generator.config.rules.NamingStrategy;// 生成mybatis-plus 相关的 controller service dao 实体类 === easycodepublicclassTestAutoGenerate{publicstaticvoidmain(String[] args){// Step1:代码生成器AutoGenerator mpg =newAutoGenerator();// Step2:全局配置GlobalConfig gc =newGlobalConfig();// 填写代码生成的目录(需要修改)!!!!!!!!!!!!!!!!!!!!!!!!!!// TODO:(需要修改),修改成你自己的项目所在的目录String projectPath ="D:\\code\\yuxing_film\\sa-token-demo-springboot";// 拼接出代码最终输出的目录
gc.setOutputDir(projectPath +"/src/main/java");// 配置开发者信息(可选)(需要修改)!!!!!!!!!!!!!// TODO:(需要修改),可以改成自己的名字
gc.setAuthor("zcj");// 配置是否打开目录,false 为不打开(可选)
gc.setOpen(false);// 实体属性 Swagger2 注解,添加 Swagger 依赖,开启 Swagger2 模式(可选)//gc.setSwagger2(true);// 重新生成文件时是否覆盖,false 表示不覆盖(可选)
gc.setFileOverride(false);// 配置主键生成策略,此处为 ASSIGN_ID(可选)
gc.setIdType(IdType.ASSIGN_ID);// 配置日期类型,此处为 ONLY_DATE(可选)
gc.setDateType(DateType.ONLY_DATE);// 默认生成的 service 会有 I 前缀
gc.setServiceName("%sService");
mpg.setGlobalConfig(gc);// Step3:数据源配置(需要修改)DataSourceConfig dsc =newDataSourceConfig();// 配置数据库 url 地址!!!!!!!!!!!// TODO:(需要修改)
dsc.setUrl("jdbc:mysql://localhost:3306/sa-token-db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8");// dsc.setSchemaName("testMyBatisPlus"); // 可以直接在 url 中指定数据库名// 配置数据库驱动
dsc.setDriverName("com.mysql.cj.jdbc.Driver");// 配置数据库连接用户名!!!!!!!!!!!!!!!!// TODO:(需要修改)
dsc.setUsername("root");// 配置数据库连接密码!!!!!!!!!!!!!!!!!// TODO:(需要修改)
dsc.setPassword("root");
mpg.setDataSource(dsc);// Step:4:包配置PackageConfig pc =newPackageConfig();// 配置父包名(需要修改)// TODO:(需要修改)
pc.setParent("com");// 配置模块名(需要修改)// TODO:(需要修改)
pc.setModuleName("pj");// 配置 entity 包名
pc.setEntity("entity");// 配置 mapper 包名
pc.setMapper("mapper");// 配置 service 包名
pc.setService("service");// 配置 controller 包名
pc.setController("controller");
mpg.setPackageInfo(pc);// Step5:策略配置(数据库表配置)StrategyConfig strategy =newStrategyConfig();// student_tb 针对学生表 生成 增删改查 controller service dao entity// * 针对所有的表生成增删改查 !!!!!!!!!!!!// TODO:(需要修改),修改成自己要生成的数据库表名
strategy.setInclude("users");// 配置数据表与实体类名之间映射的策略
strategy.setNaming(NamingStrategy.underline_to_camel);// 配置数据表的字段与实体类的属性名之间映射的策略
strategy.setColumnNaming(NamingStrategy.underline_to_camel);// 配置 lombok 模式
strategy.setEntityLombokModel(true);// 配置 rest 风格的控制器(@RestController)
strategy.setRestControllerStyle(true);// 配置驼峰转连字符
strategy.setControllerMappingHyphenStyle(true);// 配置表前缀,生成实体时去除表前缀// 此处的表名为 test_mybatis_plus_user,模块名为 test_mybatis_plus,去除前缀后剩下为 user。
strategy.setTablePrefix(pc.getModuleName()+"_");
mpg.setStrategy(strategy);// Step6:执行代码生成操作
mpg.execute();}}
生成完成,在con.pj包下自动生成了下列文件,需要把mapper/xml/UsersMapper.xml文件放到resources/mapper文件夹内,没有这个文件夹的话,就创建一个

最终的目录是这样的

二,登录认证
OK,准备工作已经做完了,现在需要进行登录认证,登陆之前我们肯定要有用户,所以我们先创建一个用户,创建一个新增用户的接口,供后续登录使用
解决启动类报错问题
引入了mybatis-plus框架之后,需要在启动类上加个注解**@MapperScan(“com.pj.mapper”)**括号内是自己的mapper包,该包要被spring扫描到,否则启动会报错
SaTokenDemoApplication.java类
@SpringBootApplication@MapperScan("com.pj.mapper")publicclassSaTokenDemoApplication{publicstaticvoidmain(String[] args)throwsJsonProcessingException{SpringApplication.run(SaTokenDemoApplication.class, args);System.out.println("启动成功,Sa-Token 配置如下:"+SaManager.getConfig());}}
1.创建新增用户接口
UsersController.java类
@RestController@RequestMapping("/user")publicclassUsersController{@AutowiredprivateUsersService usersService;/**
* 新增用户
*/@PostMapping("/savaUser")publicR<String>savaUser(@RequestBodyUsers user){return usersService.savaUser(user);}/**
* 查询全部用户,下面这个方法也是mybatis-plus自带的查询方法,很方便
*/@GetMapping("/selectAllUser")publicList<Users>queryAllUser(){return usersService.list();}}
UsersServiceImpl.java类
该类是业务层,实现了UsersService接口,在此省略此接口的内容,
该类中有用到PasswordUtil.java工具类,这个工具类的作用是对明文密码进行加密,对加密密码进行解密并对比
@ServicepublicclassUsersServiceImplextendsServiceImpl<UsersMapper,Users>implementsUsersService{@AutowiredprivateUsersMapper usersMapper;@OverridepublicR<String>savaUser(Users user){// TODO:mybatis-plus自带的有部分增删改查方法,所以mapper.java类就没有写下面那两个方法,该方法是自带的//查询数据库中是否有相同的用户if(usersMapper.selectById(user.getUserId())!=null){returnR.failed("新增用户失败,用户已存在");}Users users =newUsers();
users.setUserId(user.getUserId());
users.setUserName(user.getUserName());//密码加密存储到数据库中String hashedPassword =PasswordUtil.encryptPassword(user.getPassword());
users.setPassword(hashedPassword);
users.setUserType(user.getUserType());int insert = usersMapper.insert(users);if(insert <=0){returnR.failed("新增用户失败");}returnR.ok("新增用户成功");}}
PasswordUtil.java工具类
publicclassPasswordUtil{/**
* 加密密码
*
* @param plainTextPassword 明文密码
* @return 加密后的密码哈希值
*/publicstaticStringencryptPassword(String plainTextPassword){returnBCrypt.hashpw(plainTextPassword,BCrypt.gensalt());}/**
* 验证密码
*
* @param plainTextPassword 明文密码
* @param hashedPassword 储存在数据库中的密码哈希值
* @return 如果密码匹配,返回true,否则返回false
*/publicstaticbooleanverifyPassword(String plainTextPassword,String hashedPassword){returnBCrypt.checkpw(plainTextPassword, hashedPassword);}}
2.测试新增用户接口
接下来让我们重启一下项目,使用测试工具测试一下接口是否可用
调用该接口是成功的

数据库中也有这条数据,userType属性,后续使用权限校验会用到

接下来,我们就可以测试Sa-Token自带的登录认证了
3.配置拦截器
在com.pj包下创建一个config包,在这个config包内创建SaTokenConfig.java配置类,配置我们的拦截器
配置sa-token拦截器,会自动拦截登录接口之外的所有请求,检查有没有携带正确的Cookie,正确则放行,错误则爆出异常,异常码500,前端可以根据这个状态码来做一些操作
只有登录过后,才能请求其他接口
@ConfigurationpublicclassSaTokenConfigimplementsWebMvcConfigurer{/**
* 拦截器
*/@OverridepublicvoidaddInterceptors(InterceptorRegistry registry){// 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
registry.addInterceptor(newSaInterceptor(handle ->StpUtil.checkLogin())).addPathPatterns("/**")// 拦截所有请求.excludePathPatterns("/user/login");// 放行登录接口}}
4.创建login登录接口
登录接口最好是重新创建一个controller类,service接口,以及接口的Impl实现类,我们为了节约时间,就直接在UsersController层编写了
首先创建一个LoginService接口和接口的实现类LoginServiceImpl
在这两个类里面实现登录认证
UsersController.java类
在这个类后面追加一个登录接口
/**
* 登录
*/@PostMapping("/login")publicRlogin(@RequestBodyLoginBody loginBody){return usersService.login(loginBody);}
UsersServiceImpl接口
因为controller层调用的是usersService接口,所以我们在这里面编写具体的登录代码
/**
* 登录
*/@OverridepublicRlogin(LoginBody loginBody){// 第一步:首先判断一下入参是否为空if(loginBody ==null|| loginBody.getUserName().isEmpty()|| loginBody.getPassword().isEmpty()){returnR.failed("用户名或密码为空");}// 第二步:不为空的话,检查密码是否正确,根据用户名去数据库查找对应的用户信息,得到存储的暗文密码QueryWrapper<Users> usersQueryWrapper =newQueryWrapper<>();
usersQueryWrapper.eq("user_name",loginBody.getUserName());Users user = usersMapper.selectOne(usersQueryWrapper);String password = user.getPassword();if(!PasswordUtil.verifyPassword(loginBody.getPassword(),password)){returnR.failed("密码错误");}// 第三步:使用sa-token登录认证StpUtil.login(loginBody.getUserName());// 第四步:获取登录认证的token,返回前端// 获取 Token 相关参数SaTokenInfo tokenInfo =StpUtil.getTokenInfo();SaResult data =SaResult.data(tokenInfo);returnR.ok(data);}
5.测试登录接口
测试登录接口,登录成功

因为我们配置了拦截器,只有先登录之后,才可以访问查询接口

6.测试登录认证功能
此时我们重启项目,直接访问查询接口
这是没有登录的情况,直接报错了,项目每次重新都要先登录,否则无法访问其他的接口
由此可见,我们配置的sa-token登录认证功能和配置的拦截器是生效的

三,权限校验
1.改造登录代码,授予用户权限
简单介绍:
权限校验是针对不同的用户来说的,管理员用户拥有所有的接口访问权限,包括增删改查.但是对于别的用户,比如访客来说,就只能查看数据,不能进行增删改,所以我们需要针对每个用户的不同,赋予每个用户不同的权限,然后在访问改接口的时候校验权限,如果有该权限的话,就允许访问该接口,否则不行!
接下来我们对登录业务层代码进行改造,针对当前登录用户,给予不同的权限
UsersServiceImpl.java类
上帝权限
当一个账号拥有
"*"权限时,他可以验证通过任何权限码 (角色认证同理)
/**
* 登录
*/@OverridepublicRlogin(LoginBody loginBody){// 第一步:首先判断一下入参是否为空if(loginBody ==null|| loginBody.getUserName().isEmpty()|| loginBody.getPassword().isEmpty()){returnR.failed("用户名或密码为空");}// 第二步:不为空的话,检查密码是否正确,根据用户名去数据库查找对应的用户信息,得到存储的暗文密码QueryWrapper<Users> usersQueryWrapper =newQueryWrapper<>();
usersQueryWrapper.eq("user_name",loginBody.getUserName());Users user = usersMapper.selectOne(usersQueryWrapper);String password = user.getPassword();if(!PasswordUtil.verifyPassword(loginBody.getPassword(),password)){returnR.failed("密码错误");}// 第三步:使用sa-token登录认证StpUtil.login(loginBody.getUserName());//第四步:根据用户角色不的不同,赋予不同的权限List<String> permissionListResult =setUsernamePermission(user.getUserType());// 把该用户的权限存储到 Sa-Token的session中StpUtil.getSession().set("permissionList", permissionListResult);// 第五步:获取登录认证的token,返回前端SaTokenInfo tokenInfo =StpUtil.getTokenInfo();SaResult data =SaResult.data(tokenInfo);returnR.ok(data);}/**
* 根据用户类型的不同授予不同的权限
* 1:管理员 2:访客
*/publicList<String>setUsernamePermission(Integer userType){ArrayList<String> permissionList =newArrayList<>();switch(userType){case1:// 1:代表管理员权限,拥有所有权限
permissionList.add("*");return permissionList;case2:// 2:代表访客权限,可以查看用户信息,不能增删改用户信息Collections.addAll(permissionList,"/select");return permissionList;default:returnnull;}}
2.新建一个类,实现
StpInterface
接口
创建interceptor包,把这个类放到这个包里面
该类实现了StpInterface接口,Sa-Token框架在这个接口内做了一下权限校验的操作,只需要获取权限列表就可以了
@ComponentpublicclassStpInterfaceImplimplementsStpInterface{/**
* 返回指定账号id所拥有的权限码集合
*
* @param loginId 账号id
* @param loginType 账号类型
* @return 该账号id具有的权限码集合
*/@OverridepublicList<String>getPermissionList(Object loginId,String loginType){// 从session中获取权限列表return(List<String>)StpUtil.getSession().get("permissionList");}/**
* 返回指定账号id所拥有的角色标识集合
*
* @param loginId 账号id
* @param loginType 账号类型
* @return 该账号id具有的角色标识集合
*/@OverridepublicList<String>getRoleList(Object loginId,String loginType){// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色List<String> list =newArrayList<String>();
list.add("admin");
list.add("super-admin");return list;}}
3.在控制层方法内设置访问权限
UsersController.java
只需要添加一行代码即可
更具体的权限认证操作可以访问Sa-Token官网: 权限认证 (sa-token.cc)
/**
* 新增用户
*/@PostMapping("/savaUser")publicR<String>savaUser(@RequestBodyUsers user){// 新增用户需要/insert权限StpUtil.checkPermission("/insert");return usersService.savaUser(user);}/**
* 查询全部用户
*/@GetMapping("/selectAllUser")publicList<Users>queryAllUser(){// 查询全部用户需要/select权限StpUtil.checkPermission("/select");return usersService.list();}
4.测试权限校验
我们针对两个用户分别测试,管理员和访客,所以需要增加一个测试用户,用户类型设置为访客,也就是2
此时我们新增用户,却发现,报错了,500,也就是未登录,Sa-Token框架检测到我们没有登录就想要访问新增接口,这是不允许的,所以我们需要先登录

登录过后,在新增用户.成功

4.1 测试管理员权限
此时我们有了两个用户,我们先测试一下管理员的权限,测试方法很简单,用管理员的账号登录,然后访问新增和查询接口,看看是不是都能访问成功
第一步:我们先用管理员账户登录,登录成功

第二步:访问查询和新增接口
可以看到,新增接口访问成功,新增加了一个用户,我们可以查询全部用户看一下是否新增成功

可以看到,test2用户是新增成功的,
管理员拥有*上帝权限,可以访问所有的接口

接下来,让我们测试访客权限
4.2 测试访客权限
测试方法也很简单,用访客的账号登录,然后访问新增和查询接口,看看是不是都能访问成功
第一步:使用test访客账号登录
登录成功

第二步:访问新增接口,新增一个用户test3
没有权限,添加失败

此时我们再访问一下查询接口
是访问成功的,和管理员权限访问的结果一样

可以看到,我们设置的权限校验功能是成功的,大家快去试试吧
四,总结
文章到此就结束了,大家快去试试吧。因为我也是个编程小白,所以文章有什么错误或者是需要优化的地方,还请大佬不吝赐教,指点一二,感谢大家捧场,我也会在文章末尾供上源码地址,有需要的还请自取。祝大家都能成为更好的自己,加油!
通过百度网盘分享的文件:sa-token-demo-springboot
链接:https://pan.baidu.com/s/1OdzK4me__1gS66oz_4Zksw
提取码:7777
版权归原作者 qhj_1114 所有, 如有侵权,请联系我们删除。