课程名称: SpringBoot+Vue3 项目实战,打造企业级在线办公系统
课程章节: 第三章 基于RBAC,实现用户模块(大鹏一日同风起,扶摇直上九万里)
主讲老师: 神思者
今天主要学习的内容有:
上面时序图中,后端项目最终返回给前端的R对象中包含两种数据,一个是布尔值代表登陆结果,另一个是该用户的拥有的权限列表。因为在前端需要根据权限判断用户是否可以看到某些栏目和执行某些操作。
tb_user
数据表中保存的用户信息里面,password字段的值是经过加密存储的,而且不存在全局密钥。在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()函数。其分别的作用为:
AES_ENCRPT()
,解密的函数叫做AES_DECRPT()
在TbUserDao.java
接口中定义抽象的Dao方法,与SQL语句对应。
@Mapper public interface TbUserDao { …… public Integer login(HashMap param); }
在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; } }
首先需要创建一个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 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。
设计思路
对于一些登录之后才能访问的接口(例如:查询我的账号资料),我们通常的做法是增加一层接口校验:
如果校验通过,则:正常返回数据。
如果校验未通过,则:抛出异常,告知其需要先进行登录。
那么,判断会话是否登录的依据是什么?我们先来简单分析一下登录访问流程:
用户提交 name + password 参数,调用登录接口。
登录成功,返回这个用户的 Token 会话凭证。
用户后续的每次请求,都携带上这个 Token。
服务器根据 Token 判断此会话是否登录成功。
所谓登录认证,指的就是服务器校验账号密码,为用户颁发 Token 会话凭证的过程,这个 Token 也是我们后续通过接口校验的关键所在。
// 会话登录:参数填写要登录的账号id,建议的数据类型:long | int | String, 不可以传入复杂类型,如:User、Admin 等等 StpUtil.login(Object id);
只此一句代码,便可以使会话登录成功,实际上,Sa-Token 在背后做了大量的工作,包括但不限于: