课程名称: SpringBoot+Vue3 项目实战,打造企业级在线办公系统
课程章节: 第三章 基于RBAC,实现用户模块(大鹏一日同风起,扶摇直上九万里)
主讲老师: 神思者
课程内容
今天主要学习的内容有:
- 了解了整体项目的用户登录流程。
- 完成了用户登录的模块开发。
用户登录流程图
上面时序图中,后端项目最终返回给前端的R对象中包含两种数据,一个是布尔值代表登陆结果,另一个是该用户的拥有的权限列表。因为在前端需要根据权限判断用户是否可以看到某些栏目和执行某些操作。
用户信息加密与解密
- 在
tb_user
数据表中保存的用户信息里面,password字段的值是经过加密存储的,而且不存在全局密钥。 - 用每个用户的username作为密钥,对password字段加密。
编写后端登录接口服务
Dao层逻辑
在TbUserDao.xml
文件中添加用于登陆的SQL语句,原理并不复杂。根据登陆页面提交的用户名和密钥,到数据库中查找是否有匹配的数据。因为数据库中的密码是加密过的,所以我们要把登陆的密码加密之后,再跟数据库中的记录做比对。
<select id="login" parameterType="HashMap" resultType="Integer">
SELECT id
FROM tb_user
WHERE username = #{username}
AND password = HEX(AES_ENCRYPT(#{password}, #{username}))
LIMIT 1;
</select>
sql当中,有两个接触的比较少的两个函数。HEX()和AES_ENCRYPT()函数。其分别的作用为:
- HEX():把字节数据转换成16进制数据(HEX)
- AES_ENCRPT():MySQL数据库提供了内置的DES加密和解密的函数,我们只需要调用即可。加密的函数叫做
AES_ENCRPT()
,解密的函数叫做AES_DECRPT()
在TbUserDao.java
接口中定义抽象的Dao方法,与SQL语句对应。
@Mapper
public interface TbUserDao {
……
public Integer login(HashMap param);
}
编写Service层代码
在UserService.java
接口中声明抽象的的登陆方法。
public interface UserService {
……
public Integer login(HashMap param);
}
在UserServiceImpl.java
中实现登陆方法,就是把查询到用户ID返回。
@Service
public class UserServiceImpl implements UserService {
……
@Override
public Integer login(HashMap param) {
Integer userId = userDao.login(param);
return userId;
}
}
编写Web层
首先需要创建一个Form类保存HTTP请求提交的数据,使用Form类的好处是可以为变量设置注解,自动完成后端验证。在com.example.emos.api.controller.form
包中创建LoginForm.java
类。
@Data
@Schema(description = "登陆表单类")
public class LoginForm {
@NotBlank(message = "username不能为空")
@Pattern(regexp = "^[a-zA-Z0-9]{5,20}$", message = "username内容不正确")
@Schema(description = "用户名")
private String username;
@NotBlank(message = "password不能为空")
@Pattern(regexp = "^[a-zA-Z0-9]{6,20}$", message = "password内容不正确")
@Schema(description = "密码")
private String password;
}
在UserController.java
类中定义Web方法用于处理登陆请求。
@RestController
@RequestMapping("/user")
@Tag(name = "UserController", description = "用户Web接口")
public class UserController {
……
@PostMapping("/login")
@Operation(summary = "登陆系统")
public R login(@Valid @RequestBody LoginForm form) {
HashMap param = JSONUtil.parse(form).toBean(HashMap.class);
Integer userId = userService.login(param);
R r = R.ok().put("result", userId != null ? true : false);
if (userId != null) {
StpUtil.setLoginId(userId);
Set<String> permissions = userService.searchUserPermissions(userId);
/*
* 因为新版的Chrome浏览器不支持前端Ajax的withCredentials,
* 导致Ajax无法提交Cookie,所以我们要取出生成的Token返回给前端,
* 让前端保存在Storage中,然后每次在Ajax的Header上提交Token
*/
String token=StpUtil.getTokenInfo().getTokenValue();
r.put("permissions",permissions).put("token",token);
}
return r;
}
}
课程收货
本系统使用的权限认证框架为sa-token。其官网为:https://sa-token.dev33.cn/
Sa-Token 介绍
Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。
sa-token 登录认证模式
设计思路
对于一些登录之后才能访问的接口(例如:查询我的账号资料),我们通常的做法是增加一层接口校验:
如果校验通过,则:正常返回数据。
如果校验未通过,则:抛出异常,告知其需要先进行登录。
那么,判断会话是否登录的依据是什么?我们先来简单分析一下登录访问流程:
用户提交 name + password 参数,调用登录接口。
登录成功,返回这个用户的 Token 会话凭证。
用户后续的每次请求,都携带上这个 Token。
服务器根据 Token 判断此会话是否登录成功。
所谓登录认证,指的就是服务器校验账号密码,为用户颁发 Token 会话凭证的过程,这个 Token 也是我们后续通过接口校验的关键所在。
登录与注销
// 会话登录:参数填写要登录的账号id,建议的数据类型:long | int | String, 不可以传入复杂类型,如:User、Admin 等等
StpUtil.login(Object id);
只此一句代码,便可以使会话登录成功,实际上,Sa-Token 在背后做了大量的工作,包括但不限于:
- 检查此账号是否已被封禁
- 检查此账号是否之前已有登录
- 为账号生成 Token 凭证与 Session 会话
- 通知全局侦听器,xx 账号登录成功
- 将 Token 注入到请求上下文
- 等等其它工作……