SpringBoot+Redis实现多端登录+token自动续期和定期刷新+自定义注解和拦截器实现鉴权(角色和权限校验)

Posted 符华-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot+Redis实现多端登录+token自动续期和定期刷新+自定义注解和拦截器实现鉴权(角色和权限校验)相关的知识,希望对你有一定的参考价值。

目录

前言

前面写了几篇鉴权框架SaToken的使用的文章,用这个框架我们很容易就实现了多端登录、单点登录、鉴权等这些功能(详情可以看看这两篇链接)

【SaToken使用】springboot+redis+satoken权限认证

【SaToken使用】SpringBoot整合SaToken(一)token自动续期+token定期刷新+注解鉴权

然后又写了如果项目中没有权限这些东西,不使用SaToken,如何实现多端登录、防重登录、token续期等需求的文章:

SpringBoot+Redis 防止用户重复登录

现在我闲来无事,我想在多端登录、防重登录、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权限认证

SpringSecurity注解鉴权(整合springboot,jwt,redis)

SpringBoot+JWT+Redis实现单用户登录

springboot+redis实现session共享