0


springboot整合shiro+jwt+redis实现权限校验,项目实战,有开源项目

文章目录

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

三个核心组件:Subject, SecurityManager 和 Realms.
Subject
即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。

Subject:
代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。

Realm
Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。

项目地址

关注

I am Walker

回复

shiro

即可

使用案例

1、新建表

主要有下列5个表,分别为:

用户、角色、菜单、用户角色关联表、角色菜单关联表

是比较符合我们基本的业务需求的
image.png
然后这里提供测试的sql结构和数据

SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS =0;-- ------------------------------ Table structure for sys_menu-- ----------------------------DROPTABLEIFEXISTS`sys_menu`;CREATETABLE`sys_menu`(`id`int(0)NOTNULLAUTO_INCREMENTCOMMENT'id',`name`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'名称',`path`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'路径',`perm`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'权限',`create_by`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'创建人',`create_time`datetime(0)NULLDEFAULTNULLCOMMENT'创建时间',`update_by`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'更新人',`update_time`datetime(0)NULLDEFAULTNULLCOMMENT'更新时间',PRIMARYKEY(`id`)USINGBTREE)ENGINE=InnoDBAUTO_INCREMENT=3CHARACTERSET= utf8mb4 COLLATE= utf8mb4_0900_ai_ci COMMENT='菜单' ROW_FORMAT = Dynamic;-- ------------------------------ Records of sys_menu-- ----------------------------INSERTINTO`sys_menu`VALUES(1,'用户查询',NULL,'user:list',NULL,NULL,NULL,NULL);INSERTINTO`sys_menu`VALUES(2,'用户新增',NULL,'user:add',NULL,NULL,NULL,NULL);-- ------------------------------ Table structure for sys_role-- ----------------------------DROPTABLEIFEXISTS`sys_role`;CREATETABLE`sys_role`(`id`int(0)NOTNULLAUTO_INCREMENTCOMMENT'id',`key`varchar(255)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'唯一标识',`name`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'名称',`create_by`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'创建人',`create_time`datetime(0)NULLDEFAULTNULLCOMMENT'创建时间',`update_by`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'更新人',`update_time`datetime(0)NULLDEFAULTNULLCOMMENT'更新时间',PRIMARYKEY(`id`)USINGBTREE)ENGINE=InnoDBAUTO_INCREMENT=3CHARACTERSET= utf8mb4 COLLATE= utf8mb4_0900_ai_ci COMMENT='角色表' ROW_FORMAT = Dynamic;-- ------------------------------ Records of sys_role-- ----------------------------INSERTINTO`sys_role`VALUES(1,'admin','超级管理员',NULL,NULL,NULL,NULL);-- ------------------------------ Table structure for sys_role_menu-- ----------------------------DROPTABLEIFEXISTS`sys_role_menu`;CREATETABLE`sys_role_menu`(`id`int(0)NOTNULLAUTO_INCREMENTCOMMENT'id',`role_id`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'角色id',`menu_id`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'菜单id',`create_by`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'创建人',`create_time`datetime(0)NULLDEFAULTNULLCOMMENT'创建时间',`update_by`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'更新人',`update_time`datetime(0)NULLDEFAULTNULLCOMMENT'更新时间',PRIMARYKEY(`id`)USINGBTREE)ENGINE=InnoDBAUTO_INCREMENT=2CHARACTERSET= utf8mb4 COLLATE= utf8mb4_0900_ai_ci COMMENT='角色-菜单-关联表' ROW_FORMAT = Dynamic;-- ------------------------------ Records of sys_role_menu-- ----------------------------INSERTINTO`sys_role_menu`VALUES(1,'1','1',NULL,NULL,NULL,NULL);-- ------------------------------ Table structure for sys_user-- ----------------------------DROPTABLEIFEXISTS`sys_user`;CREATETABLE`sys_user`(`id`int(0)NOTNULLAUTO_INCREMENTCOMMENT'id',`name`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'姓名',`username`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'用户名',`password`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'密码',`create_by`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'创建人',`create_time`datetime(0)NULLDEFAULTNULLCOMMENT'创建时间',`update_by`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'更新人',`update_time`datetime(0)NULLDEFAULTNULLCOMMENT'更新时间',PRIMARYKEY(`id`)USINGBTREE)ENGINE=InnoDBAUTO_INCREMENT=4CHARACTERSET= utf8mb4 COLLATE= utf8mb4_0900_ai_ci COMMENT='用户表' ROW_FORMAT = Dynamic;-- ------------------------------ Records of sys_user-- ----------------------------INSERTINTO`sys_user`VALUES(1,'超级','admin','e10adc3949ba59abbe56e057f20f883e',NULL,NULL,NULL,NULL);-- ------------------------------ Table structure for sys_user_role-- ----------------------------DROPTABLEIFEXISTS`sys_user_role`;CREATETABLE`sys_user_role`(`id`int(0)NOTNULLAUTO_INCREMENTCOMMENT'id',`user_id`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'用户id',`role_id`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'角色id',`create_by`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'创建人',`create_time`datetime(0)NULLDEFAULTNULLCOMMENT'创建时间',`update_by`varchar(32)CHARACTERSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULLDEFAULTNULLCOMMENT'更新人',`update_time`datetime(0)NULLDEFAULTNULLCOMMENT'更新时间',PRIMARYKEY(`id`)USINGBTREE)ENGINE=InnoDBAUTO_INCREMENT=3CHARACTERSET= utf8mb4 COLLATE= utf8mb4_0900_ai_ci COMMENT='用户-角色关联表' ROW_FORMAT = Dynamic;-- ------------------------------ Records of sys_user_role-- ----------------------------INSERTINTO`sys_user_role`VALUES(1,'1','1',NULL,NULL,NULL,NULL);SET FOREIGN_KEY_CHECKS =1;

数据如下:
用户,admin,密码123456
角色:超级管理员 admin
菜单:user:list 用户查询
注:这里只是测试使用、如果是实际案例的话,超级管理员是拥有全部权限的

2、代码生成和mybatisplus整合

这里具体的可以查看该文章
生成对应的类controller、service、serviceImpl、mapper、mapper.xml等

3、编写AuthenticationToken实现类

这个目的主要是在Realm的认证和授权的时候,能够获取到token

packagecom.walker.shiro.common.config.shiro;importlombok.AllArgsConstructor;importorg.apache.shiro.authc.AuthenticationToken;/**
* author:walker
* time: 2023/2/10
* description:  AuthenticationToken的实现类
*/@AllArgsConstructorpublicclassJwtTokenimplementsAuthenticationToken{privateString token;@OverridepublicObjectgetPrincipal(){return token;}@OverridepublicObjectgetCredentials(){return token;}}
4、realm配置

该为主要用于配置认证和权限等

packagecom.walker.shiro.common.config.shiro;importcn.hutool.core.collection.CollUtil;importcom.alibaba.fastjson.JSON;importcom.walker.shiro.common.utils.Assert;importcom.walker.shiro.common.utils.JWTUtils;importcom.walker.shiro.domain.model.SysMenu;importcom.walker.shiro.domain.model.SysRole;importcom.walker.shiro.domain.model.SysUser;importlombok.extern.slf4j.Slf4j;importorg.apache.shiro.authc.AuthenticationException;importorg.apache.shiro.authc.AuthenticationInfo;importorg.apache.shiro.authc.AuthenticationToken;importorg.apache.shiro.authc.SimpleAuthenticationInfo;importorg.apache.shiro.authz.AuthorizationInfo;importorg.apache.shiro.authz.SimpleAuthorizationInfo;importorg.apache.shiro.realm.AuthorizingRealm;importorg.apache.shiro.subject.PrincipalCollection;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importjava.util.List;importjava.util.stream.Collectors;/**
* author:walker
* time: 2023/2/10
* description:  token Realm 用于配置认证和权限等
*/@Component@Slf4j//继承AuthorizingRealmpublicclassTokenRealmextendsAuthorizingRealm{@AutowiredprivateJWTUtils jwtUtils;/**
    * 生效条件,因为realm可以有很多个,所以需要进行设置
    */@Overridepublicbooleansupports(AuthenticationToken token){return token instanceofJwtToken;}/**
    *  权限配置
    */@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principalCollection){String token = principalCollection.toString();Assert.isBlank(token,"token不存在,请先登录");SysUser userEntity = jwtUtils.parseToken(token,SysUser.class);Assert.isNull(userEntity,"token解析失败,请重新登录");
        log.info("登录用户信息:{}",JSON.toJSONString(userEntity));SimpleAuthorizationInfo authorizationInfo =newSimpleAuthorizationInfo();//设置角色List<SysRole> roles = userEntity.getRoles();Assert.isCollEmpty(roles,"该用户未绑定角色");

        authorizationInfo.addRoles(roles.stream().map(SysRole::getKey).collect(Collectors.toSet()));//设置权限for(SysRole role : roles){List<SysMenu> menuEntityList = role.getMenus();if(CollUtil.isEmpty(menuEntityList)){continue;}
            authorizationInfo.addStringPermissions(menuEntityList.stream().map(SysMenu::getPerm).collect(Collectors.toSet()));}
        log.info("用户{} shiro角色:{} 权限:{}",userEntity.getUsername(),authorizationInfo.getRoles(),authorizationInfo.getStringPermissions());return authorizationInfo;}/**
    * 认证配置
    */@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationToken authenticationToken)throwsAuthenticationException{String token =(String) authenticationToken.getCredentials();returnnewSimpleAuthenticationInfo(token,token,getName());}}
5、token过滤器
packagecom.walker.shiro.common.config.shiro;importcn.hutool.core.util.StrUtil;importcom.walker.shiro.common.constants.RedisConstant;importcom.walker.shiro.common.properties.JWTProperties;importcom.walker.shiro.common.utils.HttpUtils;importlombok.AllArgsConstructor;importlombok.SneakyThrows;importorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;importorg.springframework.data.redis.core.StringRedisTemplate;importjavax.servlet.ServletRequest;importjavax.servlet.ServletResponse;importjavax.servlet.http.HttpServletRequest;/**
 * author:walker
 * time: 2023/2/10
 * description:  token过滤器
 */publicclassTokenFilterextendsBasicHttpAuthenticationFilter{/**
    * 这里因为TokenFilter不是容器,所以需要设置属性,然后通过初始化的时候传递
    */privateStringRedisTemplate redisTemplate;privateJWTProperties jwtProperties;publicTokenFilter(StringRedisTemplate redisTemplate,JWTProperties jwtProperties){this.redisTemplate = redisTemplate;this.jwtProperties = jwtProperties;}/**
    * isAccessAllowed:是否允许访问
    */@OverrideprotectedbooleanisAccessAllowed(ServletRequest request,ServletResponse response,Object mappedValue){if(isLoginAttempt(request, response)){try{executeLogin(request, response);}catch(Exception e){
                e.printStackTrace();}returntrue;}returnfalse;}/**
     * onAccessDenied:拒绝访问时,需要做什么处理
     * 如果isAccessAllowed方法返回True,则不会再调用onAccessDenied方法,如果isAccessAllowed方法返回Flase,则会继续调用onAccessDenied方法。
     * 而onAccessDenied方法里面则是具体执行登陆的地方。由于我们已经登陆,所以此方法就会返回True(filter放行),所以上面的onPreHandle方法里面的onAccessDenied方法就不会被执行。
     *
    */@OverrideprotectedbooleanonAccessDenied(ServletRequest request,ServletResponse response)throwsException{//这里直接返回false,如果不支持访问时,直接为false即可returnfalse;}/**
    * 尝试登录
    */@SneakyThrows@OverrideprotectedbooleanisLoginAttempt(ServletRequest request,ServletResponse response){HttpServletRequest req=(HttpServletRequest) request;String token = req.getHeader(jwtProperties.getHeader());if(StrUtil.isEmpty(token)){HttpUtils.resp(response,"请先进行登录");returnfalse;}//判断token是否存在,如果不存在则证明失效或者过期String s = redisTemplate.opsForValue().get(RedisConstant.TOKEN_USER_KEY+ token);if(StrUtil.isEmpty(s)){HttpUtils.resp(response,"token已失效/不存在,请重新登录");returnfalse;}returntrue;}/**
    *  执行登录
    */@OverrideprotectedbooleanexecuteLogin(ServletRequest request,ServletResponse response)throwsException{HttpServletRequest req=(HttpServletRequest) request;String token = req.getHeader(jwtProperties.getHeader());JwtToken jwtToken =newJwtToken(token);getSubject(request, response).login(jwtToken);returntrue;}}
6、shiro配置
packagecom.walker.shiro.common.config.shiro;importcom.walker.shiro.common.properties.JWTProperties;importcom.walker.shiro.common.utils.JWTUtils;importorg.apache.shiro.mgt.DefaultSessionStorageEvaluator;importorg.apache.shiro.mgt.DefaultSubjectDAO;importorg.apache.shiro.mgt.SecurityManager;importorg.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;importorg.apache.shiro.spring.web.ShiroFilterFactoryBean;importorg.apache.shiro.web.mgt.DefaultWebSecurityManager;importorg.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.redis.core.StringRedisTemplate;importjavax.servlet.Filter;importjava.util.HashMap;/**
*  shiro配置
*/@ConfigurationpublicclassShiroConfig{@AutowiredprivateTokenRealm tokenRealm;@AutowiredprivateJWTProperties jwtProperties;@AutowiredprivateStringRedisTemplate redisTemplate;//免校验StringANON="anon";//filter名称 tokenStringFILTER_TOKEN="token";//所有路径StringALL_PATH="/**";/**
     * 过滤工厂
     * 配置哪些请求需要过滤器,哪些不需要
     */@BeanpublicShiroFilterFactoryBeanshiroFilterFactoryBean(SecurityManager securityManager){ShiroFilterFactoryBean bean =newShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);//添加token过滤器HashMap<String,Filter> filterHashMap =newHashMap<>(1);
        filterHashMap.put(FILTER_TOKEN,newTokenFilter(redisTemplate,jwtProperties));
        bean.setFilters(filterHashMap);//请求过滤//需要认证:authc 不需要认证:anon//token: 需要经过token过滤器处理HashMap<String,String> map =newHashMap<>();//白名单String[] whiteList = jwtProperties.getWhiteList().split(",");for(String s : whiteList){
            map.put(s,ANON);}
        map.put(ALL_PATH,FILTER_TOKEN);
        bean.setFilterChainDefinitionMap(map);return bean;}/**
     * 安全管理器
     * 设置realm和关闭session
     */@BeanpublicSecurityManagersecurityManager(){DefaultWebSecurityManager manager =newDefaultWebSecurityManager();//设置realm
        manager.setRealm(tokenRealm);//关闭shiro自带的session,现在使用jwt 不需要sessionDefaultSubjectDAO subjectDAO =newDefaultSubjectDAO();DefaultSessionStorageEvaluator sessionStorageEvaluator =newDefaultSessionStorageEvaluator();
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator);
        manager.setSubjectDAO(subjectDAO);return manager;}/**
     * 根据spring-framework-reference,DefaultAdvisorAutoProxyCreator创建代理更加通用强大,使用此机制包括:
     * a.指定一个DefaultAdvisorAutoProxyCreator Bean的定义.
     * b.指定在相同或相关的上下文中任意数量的Advisor.注意,必须是Advisor,而不仅仅是interceptor或advice.这是必要的,因为必须有一个切点被评估,以便检查每个advice到候选bean定义是否合格
     *//**
     * @ConditionalOnMissingBean,它是修饰bean的一个注解,主要实现的是,当你的bean被注册之后,如果而注册相同类型的bean,就不会成功,它会保证你的bean只有一个,即你的实例只有一个,当你注册多个相同的bean时,会出现异常
     */@Bean@ConditionalOnMissingBeanpublicDefaultAdvisorAutoProxyCreatordefaultAdvisorAutoProxyCreator(){DefaultAdvisorAutoProxyCreator proxyCreator =newDefaultAdvisorAutoProxyCreator();
        proxyCreator.setProxyTargetClass(true);return proxyCreator;}/**
     * 授权属性源顾问
     */@BeanpublicAuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor(SecurityManager securityManager){AuthorizationAttributeSourceAdvisor advisor =newAuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);return advisor;}}
7、controller测试类
packagecom.walker.shiro.domain.controller;importcom.sun.media.sound.FFT;importcom.walker.shiro.domain.component.UserComponent;importcom.walker.shiro.domain.model.common.R;importcom.walker.shiro.domain.model.form.UserLoginForm;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestBody;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjavax.validation.Valid;/**
 * <p>
 * 用户表 前端控制器
 * </p>
 *
 * @author walker
 * @since 2023-02-09
 */@RestController@RequestMapping("/sys-user")publicclassSysUserController{@AutowiredprivateUserComponent userComponent;/**
    * 登录接口
    */@PostMapping("/login")publicRlogin(@RequestBody@ValidUserLoginForm form){returnR.ok(userComponent.login(form));}/**
    * 新增用户接口
    */@PostMapping("/add")publicRadd(@RequestBody@ValidUserLoginForm form){
        userComponent.add(form);returnR.ok();}}
packagecom.walker.shiro.domain.controller;importcom.walker.shiro.domain.model.common.R;importorg.apache.shiro.authz.annotation.RequiresPermissions;importorg.apache.shiro.authz.annotation.RequiresRoles;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/test")publicclassTestController{@GetMapping("/get")publicRget(){returnR.ok("hello");}@GetMapping("/role_admin")@RequiresRoles("admin")publicRrole_admin(){returnR.ok("OK");}@GetMapping("/role_vip")//角色判断@RequiresRoles("vip")publicRrole_vip(){returnR.ok("OK");}@GetMapping("/userAdd")@RequiresPermissions("user:add")publicRuserAdd(){returnR.ok("fail");}@GetMapping("/userList")@RequiresPermissions("user:list")publicRuserList(){returnR.ok("ok");}}
8、使用postman进行测试
  • login接口

login接口因为是设置为白名单的,所以不需要Token,直接放行
可以发现能够获取到token,
image.png
之后将登录之后的token复制一下,用来其他接口的测试

  • get 接口,不带token时

image.png
可以发现,提示需要token

  • get接口, 带token

image.png
当带上token时,就ok了

  • role_admin接口

image.png
该接口需要admin这个角色,然后目前我们的admin账号刚好有该角色
image.png
发现是可以获取到结果的

  • role_vip接口

image.png
image.png
该接口需要vip角色,admin没有,所以结果提示为没有权限

  • userAdd接口

image.png
image.png
由于admin只有user:list账号,所以不具备该权限

  • userList

调用成功
image.png

总结

权限管理可以说在大部分的项目都是需要使用的了,shiro具体权限和认证的配置,是符合我们企业项目开发的,所以我们还是得学习一下,希望对你有所帮助哈


本文转载自: https://blog.csdn.net/Think_and_work/article/details/129012978
版权归原作者 WalkerShen 所有, 如有侵权,请联系我们删除。

“springboot整合shiro+jwt+redis实现权限校验,项目实战,有开源项目”的评论:

还没有评论