SpringBoot+Redis实现多端登录+token自动续期和定期刷新+自定义注解和拦截器实现鉴权(角色和权限校验)
Posted 符华-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot+Redis实现多端登录+token自动续期和定期刷新+自定义注解和拦截器实现鉴权(角色和权限校验)相关的知识,希望对你有一定的参考价值。
目录
前言
前面写了几篇鉴权框架SaToken的使用的文章,用这个框架我们很容易就实现了多端登录、单点登录、鉴权等这些功能(详情可以看看这两篇链接)
【SaToken使用】springboot+redis+satoken权限认证
【SaToken使用】SpringBoot整合SaToken(一)token自动续期+token定期刷新+注解鉴权
然后又写了如果项目中没有权限这些东西,不使用SaToken,如何实现多端登录、防重登录、token续期等需求的文章:
现在我闲来无事,我想在多端登录、防重登录、token续期这些基础上再实现注解鉴权,而且不使用SaToken,我们自定义注解+拦截器去实现。在实现的过程中,我尝试参考SaToken的源码,边阅读边自己去实现,奈何自己能力有限,啃源码的过程还是挺吃力的。。。那些具体实现要一层一层找过去,大大增加了阅读难度。。。不说了,再说下去要被自己菜哭了😭我们还是说正事吧。
思路
1、登录、token相关
首先,我按照SaToken的方式,将登录相关、退出、token相关的这些操作,全部抽出来,放到一个自定义的@Component组件中,在这里实现具体过程,其他地方我们不用关心实现步骤,只需要直接调用这里面的相关方法就行。
登录时在redis中,我们需要存三个东西:一个是token,一个是登录用户信息,还有一个是token的创建时间(用于定期刷新token)。
2、鉴权相关
一般权限设计都是用角色进行关联的,我们需要考虑每次鉴权时:用户对应的角色——>角色对应的权限,这两个东西从哪里拿?
如果从数据库拿,请求数据库是否过于频繁?
如果从缓存中拿,那么每次修改角色或菜单的权限标识时,就要更新对应的缓存,操作是否过于麻烦?
上面这两个大概就是两种获取方式的缺点,然后综合考虑之下,我选择了第二种方式,从缓存中拿角色和权限,虽然更新麻烦一点,但不用每校验一次就查一遍数据库,可以减轻数据库的压力。
既然有了思路和解决方式,那就开干!
实现
一、登录
1、先定义一个Component组件
用于实现登录和token相关的操作
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.common.base.BaseConstant;
import com.common.vo.ExceptionVo;
import com.entity.sys.SysRoleMenu;
import com.entity.sys.SysUser;
import com.service.sys.SysRoleMenuService;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;
/**
* 登录、token相关组件工具类
*/
@Component
public class LoginUtil
private RedisUtil redisUtil = SpringUtil.getBean(RedisUtil.class);
private SysRoleMenuService sysRoleMenuService = SpringUtil.getBean(SysRoleMenuService.class);
private long tokeTime = 3600;
/**
* 登录
* @param user 登录的用户信息
*/
public String login(SysUser user)
return login(user,null,tokeTime);
/**
* 登录(指定登录类型)
* @param user 登录的用户信息
* @param loginType 登录类型
*/
public String login(SysUser user,String loginType)
return login(user,loginType,tokeTime);
/**
* 登录(指定登录类型和有效时长)
* @param user 登录的用户信息
* @param loginType 登录类型
* @param time token有效期
*/
public String login(SysUser user,String loginType,long time)
String str = DigestUtil.md5Hex(String.valueOf(user.getId())); // userId加密
//获取所有包含当前用户id加密后的字符串开头的key
redisUtil.del(redisUtil.allKey(BaseConstant.cachePrefix + str + "*")); // 将旧的key删除
if (loginType != null) str = loginType + "_" + str;
String token = str + EncryptionUtil.generateRandom(32,false); // token组成为 用户id加密字符串+随机32为字符串
Map<String,Object> map = new HashMap<>();
map.put("token",token);
map.put("createTime",System.currentTimeMillis());
map.put("user",user);
redisUtil.hmset(BaseConstant.cachePrefix+token,map,time);
return token;
/**
* 退出(根据token退出)
*/
public void logout(String token)
redisUtil.del(token);
/**
* 获取当前登录用户的token
*/
public String getToken(HttpServletRequest request)
String token = request.getHeader(BaseConstant.tokenHeader);
if (token == null || token.length() == 0 || !redisUtil.hasKey(BaseConstant.cachePrefix+token))
throw new ExceptionVo(1003,"用户未登录");
return token;
/**
* 获取当前用户的剩余有效时长
*/
public long getTimeOut(String token)
return redisUtil.getExpire(BaseConstant.cachePrefix+token);
/**
* 获取当前登录信息
*/
public Map<String,Object> getLogin(String token)
return redisUtil.hmget(BaseConstant.cachePrefix+token);
/**
* 获取当前用户
*/
public SysUser getLoginUser(String token)
return (SysUser) redisUtil.hget(BaseConstant.cachePrefix+token,"user");
/**
* 获取当前用户token的创建时间
*/
public long getCreateTime(String token)
return (Long) redisUtil.hget(BaseConstant.cachePrefix+token,"createTime");
/**
* 获取当前用户的所有权限集合
*/
public List<String> getPermList(String token,List<String> roleList)
if (roleList == null)
roleList = getRoleList(token);
List<String> permList = new ArrayList<>();
if (redisUtil.hasKey(BaseConstant.rolePermList)) // 如果redis中有所有角色对应的所有权限,直接从redis中拿
Map<String, Object> map = redisUtil.hmget(BaseConstant.rolePermList);
for (String role : roleList)
permList.addAll((List<String>) map.get(role));
else
// redis没有,则去数据库查询,并将查询到的数据设置到redis中
List<SysRoleMenu> allRolePerm = sysRoleMenuService.listAll();
Map<String, List<SysRoleMenu>> group = allRolePerm.stream().collect(Collectors.groupingBy(SysRoleMenu::getRoleId));
Map<String, List<String>> map = new HashMap<>();
for (String key : group.keySet())
List<String> perms = group.get(key).stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList());
map.put(key,perms);
if (roleList.contains(key))
permList.addAll(perms);
redisUtil.hmset(BaseConstant.rolePermList,map);
return permList;
/**
* 获取当前用户的所有角色集合
*/
public List<String> getRoleList(String token)
String[] roleIds = getLoginUser(token).getRoleIds();
return Arrays.asList(roleIds);
/**
* 刷新过期时间
*/
public void updateTimeOut(String token)
updateTimeOut(token,tokeTime);
/**
* 刷新过期时间(指定过期时间)
*/
public void updateTimeOut(String token,long time)
redisUtil.hmset(BaseConstant.cachePrefix+token,getLogin(token),time);
/**
* 更新用户信息
*/
public void updateUser(String token,SysUser user)
redisUtil.hset(BaseConstant.cachePrefix+token,"user",user);
2、登录、退出
登录
@Resource
private LoginUtil loginUtil;
/** 登录 */
@PostMapping("/login")
public ResultUtil login(String userName, String password, String code,HttpServletRequest request)
try
code = code.toUpperCase();
Object verCode = redisUtil.get(BaseConstant.verCode+code);
if (Objects.isNull(verCode))
return ResultUtil.error("验证码已失效,请重新输入");
redisUtil.del(BaseConstant.verCode+code);
password = RSAUtil.decrypt(password); //密码私钥解密
SysSafe safe = sysSafeService.list().get(0);
SysUser user = passwordErrorNum(userName, password,safe);// 校验用户名密码是否正确
int i = safe.getIdleTimeSetting(); //如果系统闲置时间为0,设置token和session永不过期
String token = "";
if (i==0)
token = loginUtil.login(user,null,-1);
else
token = loginUtil.login(user);
return ResultUtil.success(token);
catch (ExceptionVo e)
return ResultUtil.error(e.getCode(),e.getMessage());
catch (Exception e)
e.printStackTrace();
return ResultUtil.error(BaseConstant.UNKNOWN_EXCEPTION);
退出
/** 退出登录 */
@DeleteMapping("/logout")
public ResultUtil logout(HttpServletRequest request)
String token = request.getHeader(BaseConstant.tokenHeader);
//根据token退出登录
loginUtil.logout(token);
return ResultUtil.success("退出登录成功");
获取当前登录用户信息
/**
* 获取当前登录用户信息
*/
@GetMapping("/getLoginUser")
public ResultUtil getLoginUserInfo()
Map<String,Object> map = new HashMap<>();
SysUser user = getLoginUser();
map.put("user",user);
//查询角色和权限
map.put("permissions",loginUtil.getPermList(token(),Arrays.asList(user.getRoleIds())));
return ResultUtil.success(map);
公共的controller类
import com.common.util.LoginUtil;
import com.entity.sys.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
public abstract class BaseController<S extends BaseService, E extends BaseEntity>
@Autowired(required=false)
protected S service;
@Resource
protected LoginUtil loginUtil;
@Resource
protected HttpServletRequest httpServletRequest;
public String token()
return service.token();
/**
* 获取当前登录的用户
*/
public SysUser getLoginUser()
return service.getLoginUser();
public String loginId()
return service.loginId();
公共的service类
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.common.util.LoginUtil;
import com.common.util.RedisUtil;
import com.entity.sys.SysUser;
import com.entity.sys.query.SysQuery;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class BaseService<M extends BaseMapper<E>, E extends BaseEntity> extends ServiceImpl<M, E>
@Resource
protected RedisUtil redisUtil;
@Resource
protected LoginUtil loginUtil;
@Resource
protected HttpServletRequest httpServletRequest;
/**
* 获取当前token
*/
public String token()
return loginUtil.getToken(httpServletRequest);
/**
* 获取当前登录的用户
*/
public SysUser getLoginUser()
return loginUtil.getLoginUser(token());
/**
* 获取当前登录用户的id
*/
public String loginIdSpringBoot+Redis实现多端登录+token自动续期和定期刷新+自定义注解和拦截器实现鉴权(角色和权限校验)
SaToken使用springboot+redis+satoken权限认证
SaToken使用springboot+redis+satoken权限认证