大家好,我是孤狼
这篇文章将为大家分享一下最近在做分布式系统中所做的一个登录功能,采用的技术是sprinSecurity+jwt 。主要的一个思路是,前端将账号密码通过POST请求发送给后端,后端拿到数据进行相应的验证,生成 toke 进行返回。
回顾
记得第一次做登录功能时,觉得很简单,在前端通过form表单将账号密码传给后端Controller层调service层调Mapper,通过编写sql,account=${account} and password=${password},这两个条件去进行检验。这其实最简单的登录功能实现。
springSecurity是什么?
springSecurity 是 spring 全家桶的成员之一,是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
Jwt是什么?
JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑用于在各方之间作为JSON对象安全地传输信息。此信息可以验证和信任,因为它是经过数字签名的。可以使用ECHMAC算法对RSA或WTAC算法进行签名。尽管JWT可以被加密以在各方之间提供保密性,但我们将重点关注签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌对其他方隐藏这些声明。当使用公钥/私钥对令牌进行签名时,签名还证明只有持有私钥的一方才是签名方。
RBAC模式是什么?
RBAC认为权限授权的过程可以抽象地概括为:Who是否可以对What进行How的访问操作,并对这个逻辑表达式进行判断是否为True的求解过程,也即是将权限问题转换为What、How的问题,Who、What、How构成了访问权限三元组;在RBAC模型里有3个基础组成部分,分别是:用户、角色和权限。
实现该模式会设计到最基本的五张表
用户表,角色表,权限表,用户-角色关联表,角色-权限关联表。
1、用户表存储用户信息,
2、角色表存储角色信息,
3、权限表存储权限信息,
4、用户-角色表,每个用户可以有多个角色,通过用户Id和角色Id将两者关联
5、角色-权限表,每个角色会有多个权限,通角色Id和权限Id将两者进行关联
在理解了上面三个技术的概念之后我来分享一下我所实现的登录功能及思路。
登录功能实现
思路:
第一步:
前端携带账号和密码发送 POST 请求给后端登录接口,后端对数据进行校验,并获取request,通过request获取ip地址,通过ip地址去redis中查询当前ip登录错误的次数,没有超过规定次数就继续往下执行,将账号、密码传给springSecurity这个安全框架的 UsernamePasswordAuthenticationToken()方法进行账号校验。
校验成功获取用户的详细信息,并通过返回的账号信息username通过JWT生成token并将token存放到redis数据库中,再将token进行返回。
关键代码:
public Result createToken(HttpServletRequest request, LoginUserQo loginUserQo) {
// 判断用户和密码不为空
if (null == loginUserQo.getUsername() && null == loginUserQo.getPassword()) {
throw new BlogsException(BlogsHttpStatus.USER_PASSWORD_EMPTY);
}
String ip = IpUtils.getIpAddr(request);
String limitCount = redisUntils.getCacheObject(RedisConf.LOGIN_LIMIT + RedisConf.SEGMENTATION + ip);
if (StringUtils.isNotEmpty(limitCount)) {
Integer limitTimeCount = Integer.valueOf(limitCount);
if (limitTimeCount >= CharNumConstants.NUM_FIVE) {
return Result.error().message("密码错误次数过多,已被锁定30分钟");
}
}
//用户认证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUserQo.getUsername(), loginUserQo.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 认证失败,设置登录次数限制
if (Objects.isNull(authenticate)) {
log.error("登录失败");
returnString.format(MessageConf.LOGIN_ERROR, setLoginCommit(request)));
}
// 认证成功,获取用户信息
SecurityUser user = (SecurityUser) authenticate.getPrincipal();
// 判断账号是否封禁
isAccout(user.getUserDTO().getAccountStatus());
// 从redis 中获取token判断是否已经登录
String token = redisUntils.getCacheObject(CacheConstants.LOGIN_TOKEN_KEY + user.getUsername());
if (Objects.isNull(token)) {
// 根据 username 生成 token
token = jwtUntils.createToken(user.getUsername());
// 将 token 存放到 redis中 过期时间为 360 分钟
redisUntils.setCacheObject(CacheConstants.LOGIN_TOKEN_KEY + user.getUsername(), token, CacheConstants.EXPIRATION, TimeUnit.MINUTES);
}
// todo: 判断redis中是否存在用户信息,并将用户信息存放到redis中 .....
return Result.ok().data("token", token);
}
思路:
第二步:
前端将获取到返回的token进行存储。携带token去访问获取用户权限,角色信息的接口。后端将获取到token通过JWT解析获取username,在通过username去登录用户的详细信息,得到userId,通过userId去关联查询角色表和用户-角色关联表获取角色信息,通过userId查询角色表和角色-菜单关联表,获取访问菜单的权限信息。将角色信息和菜单权限信息存放到redis中并将其返回。
关键代码:
public Map getUserInfoByUsername(String username) {
Map result = new HashMap<>();
SysUserDO user = userService.selectUserInfoByUsername(username);
//根据用户id获取角色
List roleList = roleService.selectRoleByUserId(user.getUserId());
List roleNameList = roleList.stream().map(item -> item.getRoleName()).collect(Collectors.toList());
if (roleNameList.size() == 0) {
//前端框架必须返回一个角色,否则报错,如果没有角色,返回一个空角色
roleNameList.add("");
}
redisUntils.setCacheList(CacheConstants.ROLE_KEY + username, roleNameList);
//根据用户id获取操作权限值
Set permissionValueList = menuService.selectPermsPermissionByUserId(user.getUserId());
// 将用户的权限列表存放到redis 中
redisUntils.setCacheSet(CacheConstants.USER_LIMIT_KEY + username, permissionValueList);
result.put("name", user.getUsername());
"avatar", user.getAvatar());
"roles", roleNameList);
"permissionValueList", permissionValueList);
return result;
}
思路:
第三步:
前端携带token访问获取菜单信息的接口,后端获取到token,通过JWT解析获取username,通过username获取当前用户的详细信息,得到userId,通过userId左连接查询用户-角色关联表,菜单-角色关联表,菜单表获取详细信息。将获取到的信息通过List接收,编写相应的工具类方法将数据进行父子菜单排序,并返回给前端进行展示。
关键代码:
public List getMenu(String token) {
//获取当前登录用户用户名
String username = jwtUntils.getUserName(token);
SysUserDO user = userService.selectUserInfoByUsername(username);
//根据用户id获取用户菜单权限
List permissionList = menuService.selectPermissionByUserId(user.getUserId());
return permissionList;
} @Override
public List selectPermissionByUserId(String userId) {
List selectPermissionList = null;
if (this.isSysAdmin(userId)) {
//如果是超级管理员,获取所有菜单
selectPermissionList = baseMapper.selectList(null);
} else {
selectPermissionList = baseMapper.selectPermissionByUserId(userId);
}
List permissionList = PermissionHelper.bulid(selectPermissionList);
List result = MemuHelper.bulid(permissionList);
return result;
}
好了,本次通过token实现登录功能,就到这里了
我是孤狼,我们下期再见
| 留言与评论(共有 0 条评论) “” |