当前位置: 首页 > news >正文

网站建设网页无锡做网站公司费用价格

网站建设网页,无锡做网站公司费用价格,有做挂名法人和股东的网站吗,wordpress大侠目录前言思路1、登录、token相关2、鉴权相关实现一、登录1、先定义一个Component组件2、登录、退出二、鉴权、token相关1、自定义注解2、拦截器鉴权、token续期和定期刷新3、新增/更新角色时,更新redis中角色对应的权限4、更新菜单权限标识时,更新redis中…

目录

  • 前言
  • 思路
    • 1、登录、token相关
    • 2、鉴权相关
  • 实现
    • 一、登录
      • 1、先定义一个Component组件
      • 2、登录、退出
    • 二、鉴权、token相关
      • 1、自定义注解
      • 2、拦截器鉴权、token续期和定期刷新
      • 3、新增/更新角色时,更新redis中角色对应的权限
      • 4、更新菜单权限标识时,更新redis中对应的权限标识
  • 最后
    • 源码

前言

前面写了几篇鉴权框架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加密后的字符串开头的keyredisUtil.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;@Resourceprotected LoginUtil loginUtil;@Resourceprotected 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>{@Resourceprotected RedisUtil redisUtil;@Resourceprotected LoginUtil loginUtil;@Resourceprotected HttpServletRequest httpServletRequest;/*** 获取当前token*/public String token(){return loginUtil.getToken(httpServletRequest);}/*** 获取当前登录的用户*/public SysUser getLoginUser(){return loginUtil.getLoginUser(token());}/*** 获取当前登录用户的id*/public String loginId(){return getLoginUser().getId();}/*** 更新redis中的权限标识*/public void updatePerm(String oldPerm,String newPerm){Map<String, Object> map = redisUtil.hmget(BaseConstant.rolePermList);for (String key : map.keySet()) {List<String> permList = (List<String>) map.get(key);int index = permList.indexOf(oldPerm); // 获取旧权限标识的索引if (index != -1){permList.set(index,newPerm); // 更新新标识}}redisUtil.hmset(BaseConstant.rolePermList,map);}/*** 删除redis中的角色与权限关联*/public void deleteRolePerm(String roleId){redisUtil.hdel(BaseConstant.rolePermList,roleId);}// ......省略其他代码}

上面我们就实现了多端登录功能,token自动续期和定期刷新是在拦截器中实现的,和鉴权的拦截器是同一个,我们放到后面说。

二、鉴权、token相关

1、自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 权限校验*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckPermission {/** 拥有指定权限 */String[] perm() default {};/** 拥有指定角色 */String[] role() default {};/*** 校验类型:*  0: 只校验权限,多个权限需要同时满足;*  1: 只校验权限,多个权限满足一个即可;*  2: 只校验角色,多个角色需要同时满足;*  3: 只校验角色,多个角色满足一个即可;*  4: 校验权限和角色,需要权限和角色同时满足;*  5: 校验权限和角色,权限和角色任意满足一个;* @return*/int type() default 0;
}

2、拦截器鉴权、token续期和定期刷新

import cn.hutool.extra.spring.SpringUtil;
import com.common.anno.CheckPermission;
import com.common.base.BaseConstant;
import com.common.util.LoginUtil;
import com.common.vo.ExceptionVo;
import com.entity.sys.SysUser;
import com.entity.sys.SysUserRole;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;/*** 自定义拦截器,实现注解鉴权和token验证*/
public class CustomInterceptor implements HandlerInterceptor {private LoginUtil loginUtil = SpringUtil.getBean(LoginUtil.class);/** 设置响应头部信息 */private void setHeader(HttpServletRequest request,HttpServletResponse response){response.setHeader( "Set-Cookie" , "cookiename=httponlyTest;Path=/;Domain=domainvalue;Max-Age=seconds;HTTPOnly");response.setHeader( "Content-Security-Policy" , "default-src 'self'; script-src 'self'; frame-ancestors 'self'");response.setHeader("Access-Control-Allow-Origin", (request).getHeader("Origin"));response.setHeader("Access-Control-Allow-Credentials", "true");response.setHeader("Referrer-Policy","no-referrer");response.setContentType("application/json");response.setCharacterEncoding("UTF-8");}/** token有效期刷新和定期刷新 */private void refreshToken(HttpServletResponse response,String tokenValue){//判断token的创建时间是否大于2小时,如果是的话则需要刷新tokenlong time = System.currentTimeMillis() - loginUtil.getCreateTime(tokenValue);long hour = time/1000/(60 * 60);if (hour>2){// TODO 获取当前用户信息,重新登录,生成新的token,将新token设置到响应头中SysUser user = loginUtil.getLoginUser(tokenValue);// TODO 这里重新登录(会把旧登录删除),生成新的tokenString newToken = loginUtil.login(user);response.setHeader(BaseConstant.tokenHeader, newToken);}long tokenTimeout = loginUtil.getTimeOut(tokenValue);// 获取过期时间if (tokenTimeout != -1){ // token没过期,过期时间不是-1的时候,每次请求都刷新过期时间loginUtil.updateTimeOut(tokenValue);}}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){setHeader(request,response);// 获取当前token(获取的是请求头的token),同时会校验token是否过期,过期了会直接抛出异常,所以这里不用做额外处理。String tokenValue = loginUtil.getToken(request);if (!checkPermisson(handler,tokenValue)){ // 鉴权throw new ExceptionVo(-1,"没有操作权限");}refreshToken(response,tokenValue);return true;}/** 校验注解权限或角色 */private boolean checkPermisson(Object handler,String tokenValue){boolean flag = true;if (handler instanceof HandlerMethod){// 获得请求的方法Method method = ((HandlerMethod) handler).getMethod();// 获得该方法上面的注解,如果没有注解,直接返回true,通过CheckPermission annotation = method.getAnnotation(CheckPermission.class);if (annotation != null) {String[] perm = annotation.perm(); // 注解上指定的权限标识String[] role = annotation.role(); // 注解上指定的角色int type = annotation.type();      // 校验的类别方式SysUser user = loginUtil.getLoginUser(tokenValue); //得到当前登录人的权限和角色List<String> roleList = user.getRoleList().stream().map(SysUserRole.UserRoleVo::getRoleKey).collect(Collectors.toList());List<String> permList = loginUtil.getPermList(tokenValue, Arrays.asList(user.getRoleIds()));if(type == 0){ // 0: 只校验权限,多个权限需要同时满足;flag = hasElement(permList, Arrays.asList(perm),true);}else if(type == 1){ // 1: 只校验权限,多个权限满足一个即可;flag = hasElement(permList, Arrays.asList(perm),false);}else if(type == 2){ // 2: 只校验角色,多个角色需要同时满足;flag = hasElement(roleList, Arrays.asList(role),true);}else if(type == 3){ // 3: 只校验角色,多个角色满足一个即可;flag = hasElement(roleList, Arrays.asList(role),false);}else if(type == 4){ // 4: 校验权限和角色,需要权限和角色同时满足;flag = hasElement(permList, Arrays.asList(perm),true);if (flag){ // 满足权限时再校验角色,否则直接返回falseflag = hasElement(roleList, Arrays.asList(role),true);}}else if(type == 5){ // 5: 校验权限和角色,权限和角色任意满足一个;flag = hasElement(permList, Arrays.asList(perm),true);if (!flag){ // 不满足权限时再校验角色,否则直接返回trueflag = hasElement(roleList, Arrays.asList(role),true);}}}}return flag;}/*** 在list中判断指定元素是否存在* @param list1 拥有的权限列表* @param list2 给定的权限列表* @param isAnd 是否要全部满足, true 全部满足,false 满足一个即可*/public static boolean hasElement(List<String> list1,List<String> list2,boolean isAnd){boolean flag = true;if (list1 != null && list1.size() != 0 && list2 != null && list2.size() != 0 && list1.size() >= list2.size()) {if (list1.contains(list2)) {return flag;} else {for (String element : list2) {boolean b = hasElement(list1,element);if (isAnd){if (!b){ // 同时满足,当有一个不满足时,直接跳出,返回falseflag = false;break;}}else {if (b){ // 满足一个即可,当有一个满足时,直接跳出,返回trueflag = true;break;}flag = false;}}}} else {return false;}return flag;}/*** 在list中判断指定元素是否存在* @param list 拥有的权限列表* @param element 给定的权限*/public static boolean hasElement(List<String> list,String element){if (list != null && list.size() != 0) {if (list.contains(element)) {return true;} else {Iterator it = list.iterator();String patt;do {if (!it.hasNext()) {return false;}patt = (String) it.next();} while(!hasElement(patt, element));return true;}} else {return false;}}/*** 判断两个元素是否匹配* @param value1 拥有的* @param value2 给定的需要匹配的元素*/private static boolean hasElement(String value1,String value2){if (value1 == null && value2 == null) {return true;} else if (value1 != null && value2 != null) {return value1.indexOf("*") == -1 ? value1.equals(value2) : Pattern.matches(value1.replaceAll("\\*", ".*"), value2);} else {return false;}}
}

测试:

在这里插入图片描述

3、新增/更新角色时,更新redis中角色对应的权限

SysMenuService

/*** 更新redis中的角色与权限关联*/
public void updateRolePerm(String roleId){SysQuery query = new SysQuery();query.setId(roleId);List<SysMenu> menuList = selectPermsByRoleId(query);List<String> permList = menuList.stream().map(SysMenu::getPerms).collect(Collectors.toList());redisUtil.hset(BaseConstant.rolePermList,roleId,permList);
}

SysRoleController

/*** 新增/修改角色*/
@PostMapping("/insert")
@CheckPermission("system:role:edit")
public ResultUtil insert(@RequestBody SysRole role) {// ......省略其他代码sysMenuService.updateRolePerm(role.getId()); // 更新缓存中角色与菜单权限关联return ResultUtil.success();
}

4、更新菜单权限标识时,更新redis中对应的权限标识

SysMenuController

/*** 修改菜单*/
@PostMapping("/update")
@CheckPermission("system:menu:update")
public ResultUtil update(@RequestBody SysMenu menu){SysMenu m = service.getById(menu.getId());// 根据菜单id获取更新前的菜单// ......省略其他代码if (!m.getPerms().equals(menu.getPerms())){ // 如果更改了权限标识,在redis缓存中也需要更改service.updatePerm(m.getPerms(),menu.getPerms());}return ResultUtil.success();
}

最后

源码

都看到最后了,不给博主一个赞再走嘛(づ ̄ 3 ̄)づ

http://www.dtcms.com/a/518750.html

相关文章:

  • 网站设计 评价 方法广州网页制作步骤
  • 商业网站开发 流程网站推广服务具体内容包括哪些
  • 网站建设中如何设置外链接我的网站突然打不开了怎么回事啊
  • 喜满堂网站建设银行官网登录入口
  • 精仿腾讯3366小游戏门户网站源码织梦最新内核带全部数据!网站在线压缩
  • 帝国建站模板地接做的网站
  • 做企业网站需要什么资料互联网站开发
  • 58网站 做现浇混凝土白山市住房和城乡建设局网站
  • 网站内链有什么用商城网站建设需要什么团队
  • 男朋友抱着我在教室做网站wordpress动
  • 常用的网站推广方法什么是网站建设策划
  • 有没有那个网站是做点心的wordpress站点统计代码
  • 网站自己做余额充值自动扣款新手怎样学校做网站
  • 高端学校网站建设自助微信小程序开发教程
  • 微餐饮建站费用网络空间安全考研
  • 淮安公司网站建设成都电子商务网站建设公司
  • 爱美刻在线制作网站简述网站推广的方式
  • 东莞网站搭建两学一做 答题 网站
  • 百度指数的网站南海网站设计
  • 把做的网站放到互联网上wordpress选择幻灯片模版没有用
  • 为什么前端都不用dw临淄关键词网站优化培训中心
  • 多个域名 指向同一个网站小说发表哪个网站赚钱
  • 浙江平台网站建设找哪家网站广告是内容营销吗
  • 招聘做微信公众号网站维护网站建设 ipv6
  • wordpress 前台评论新乡seo网站推广工具
  • 保定专业做网站的公司哪家好微网站用什么软件做
  • 网页模板网站生成邮箱qq登录网页登陆入口
  • html网站开发实例教程网站如何做免费推广
  • 阳江做网站seopython 做网站教程
  • 免费建立个人视频网站女装品牌排行榜前十名