简介

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个表,别离为:用户、角色、菜单、用户角色关联表、角色菜单关联表
是比拟合乎咱们根本的业务需要的

而后这里提供测试的sql构造和数据

SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;-- ------------------------------ Table structure for sys_menu-- ----------------------------DROP TABLE IF EXISTS `sys_menu`;CREATE TABLE `sys_menu`  (  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'id',  `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '名称',  `path` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '门路',  `perm` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限',  `create_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建人',  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创立工夫',  `update_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '更新人',  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新工夫',  PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '菜单' ROW_FORMAT = Dynamic;-- ------------------------------ Records of sys_menu-- ----------------------------INSERT INTO `sys_menu` VALUES (1, '用户查问', NULL, 'user:list', NULL, NULL, NULL, NULL);INSERT INTO `sys_menu` VALUES (2, '用户新增', NULL, 'user:add', NULL, NULL, NULL, NULL);-- ------------------------------ Table structure for sys_role-- ----------------------------DROP TABLE IF EXISTS `sys_role`;CREATE TABLE `sys_role`  (  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'id',  `key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '惟一标识',  `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '名称',  `create_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建人',  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创立工夫',  `update_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '更新人',  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新工夫',  PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;-- ------------------------------ Records of sys_role-- ----------------------------INSERT INTO `sys_role` VALUES (1, 'admin', '超级管理员', NULL, NULL, NULL, NULL);-- ------------------------------ Table structure for sys_role_menu-- ----------------------------DROP TABLE IF EXISTS `sys_role_menu`;CREATE TABLE `sys_role_menu`  (  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'id',  `role_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色id',  `menu_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '菜单id',  `create_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建人',  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创立工夫',  `update_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '更新人',  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新工夫',  PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色-菜单-关联表' ROW_FORMAT = Dynamic;-- ------------------------------ Records of sys_role_menu-- ----------------------------INSERT INTO `sys_role_menu` VALUES (1, '1', '1', NULL, NULL, NULL, NULL);-- ------------------------------ Table structure for sys_user-- ----------------------------DROP TABLE IF EXISTS `sys_user`;CREATE TABLE `sys_user`  (  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'id',  `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '姓名',  `username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户名',  `password` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '明码',  `create_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建人',  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创立工夫',  `update_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '更新人',  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新工夫',  PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;-- ------------------------------ Records of sys_user-- ----------------------------INSERT INTO `sys_user` VALUES (1, '超级', 'admin', 'e10adc3949ba59abbe56e057f20f883e', NULL, NULL, NULL, NULL);-- ------------------------------ Table structure for sys_user_role-- ----------------------------DROP TABLE IF EXISTS `sys_user_role`;CREATE TABLE `sys_user_role`  (  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'id',  `user_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户id',  `role_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色id',  `create_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建人',  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创立工夫',  `update_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '更新人',  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新工夫',  PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户-角色关联表' ROW_FORMAT = Dynamic;-- ------------------------------ Records of sys_user_role-- ----------------------------INSERT INTO `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

package com.walker.shiro.common.config.shiro;import lombok.AllArgsConstructor;import org.apache.shiro.authc.AuthenticationToken;/*** author:walker* time: 2023/2/10* description:  AuthenticationToken的实现类*/@AllArgsConstructorpublic class JwtToken implements AuthenticationToken {    private String token;    @Override    public Object getPrincipal() {        return token;    }    @Override    public Object getCredentials() {        return token;    }}
4、realm配置

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

package com.walker.shiro.common.config.shiro;import cn.hutool.core.collection.CollUtil;import com.alibaba.fastjson.JSON;import com.walker.shiro.common.utils.Assert;import com.walker.shiro.common.utils.JWTUtils;import com.walker.shiro.domain.model.SysMenu;import com.walker.shiro.domain.model.SysRole;import com.walker.shiro.domain.model.SysUser;import lombok.extern.slf4j.Slf4j;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.SimpleAuthenticationInfo;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.authz.SimpleAuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.util.List;import java.util.stream.Collectors;/*** author:walker* time: 2023/2/10* description:  token Realm 用于配置认证和权限等*/@Component@Slf4j//继承AuthorizingRealmpublic class TokenRealm extends AuthorizingRealm {    @Autowired    private JWTUtils jwtUtils;    /**    * 失效条件,因为realm能够有很多个,所以须要进行设置    */    @Override    public boolean supports(AuthenticationToken token) {        return token instanceof JwtToken;    }    /**    *  权限配置    */    @Override    protected AuthorizationInfo doGetAuthorizationInfo(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 = new SimpleAuthorizationInfo();        //设置角色        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;    }    /**    * 认证配置    */    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {        String token = (String) authenticationToken.getCredentials();        return new SimpleAuthenticationInfo(token,token,getName());    }}
5、token过滤器
package com.walker.shiro.common.config.shiro;import cn.hutool.core.util.StrUtil;import com.walker.shiro.common.constants.RedisConstant;import com.walker.shiro.common.properties.JWTProperties;import com.walker.shiro.common.utils.HttpUtils;import lombok.AllArgsConstructor;import lombok.SneakyThrows;import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;import org.springframework.data.redis.core.StringRedisTemplate;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;/** * author:walker * time: 2023/2/10 * description:  token过滤器 */public class TokenFilter extends BasicHttpAuthenticationFilter {    /**    * 这里因为TokenFilter不是容器,所以须要设置属性,而后通过初始化的时候传递    */    private StringRedisTemplate redisTemplate;    private JWTProperties jwtProperties;    public TokenFilter(StringRedisTemplate redisTemplate, JWTProperties jwtProperties) {        this.redisTemplate = redisTemplate;        this.jwtProperties = jwtProperties;    }    /**    * isAccessAllowed:是否容许拜访    */    @Override    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {        if(isLoginAttempt(request, response)){            try {                executeLogin(request, response);            } catch (Exception e) {                e.printStackTrace();            }            return true;        }        return false;    }    /**     * onAccessDenied:回绝拜访时,须要做什么解决     * 如果isAccessAllowed办法返回True,则不会再调用onAccessDenied办法,如果isAccessAllowed办法返回Flase,则会持续调用onAccessDenied办法。     * 而onAccessDenied办法外面则是具体执行登陆的中央。因为咱们曾经登陆,所以此办法就会返回True(filter放行),所以下面的onPreHandle办法外面的onAccessDenied办法就不会被执行。     *    */    @Override    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {        //这里间接返回false,如果不反对拜访时,间接为false即可        return false;    }    /**    * 尝试登录    */    @SneakyThrows    @Override    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {        HttpServletRequest req= (HttpServletRequest) request;        String token = req.getHeader(jwtProperties.getHeader());        if(StrUtil.isEmpty(token)){            HttpUtils.resp(response,"请先进行登录");            return false;        }        //判断token是否存在,如果不存在则证实生效或者过期        String s = redisTemplate.opsForValue().get(RedisConstant.TOKEN_USER_KEY + token);        if(StrUtil.isEmpty(s)){            HttpUtils.resp(response,"token已生效/不存在,请从新登录");            return false;        }        return true;    }    /**    *  执行登录    */    @Override    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {        HttpServletRequest req= (HttpServletRequest) request;        String token = req.getHeader(jwtProperties.getHeader());        JwtToken jwtToken = new JwtToken(token);        getSubject(request, response).login(jwtToken);        return true;    }}
6、shiro配置
package com.walker.shiro.common.config.shiro;import com.walker.shiro.common.properties.JWTProperties;import com.walker.shiro.common.utils.JWTUtils;import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;import org.apache.shiro.mgt.DefaultSubjectDAO;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.core.StringRedisTemplate;import javax.servlet.Filter;import java.util.HashMap;/***  shiro配置*/@Configurationpublic class ShiroConfig {    @Autowired    private TokenRealm tokenRealm;    @Autowired    private JWTProperties jwtProperties;    @Autowired    private StringRedisTemplate redisTemplate;    //免校验    String ANON="anon";    //filter名称 token    String FILTER_TOKEN="token";    //所有门路    String ALL_PATH="/**";    /**     * 过滤工厂     * 配置哪些申请须要过滤器,哪些不须要     */    @Bean    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();        bean.setSecurityManager(securityManager);        //增加token过滤器        HashMap<String, Filter> filterHashMap = new HashMap<>(1);        filterHashMap.put(FILTER_TOKEN,new TokenFilter(redisTemplate,jwtProperties));        bean.setFilters(filterHashMap);        //申请过滤        //须要认证:authc 不须要认证:anon        //token: 须要通过token过滤器解决        HashMap<String, String> map = new HashMap<>();        //白名单        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     */    @Bean    public SecurityManager securityManager(){        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();        //设置realm        manager.setRealm(tokenRealm);        //敞开shiro自带的session,当初应用jwt 不须要session        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();        DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator();        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    @ConditionalOnMissingBean    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();        proxyCreator.setProxyTargetClass(true);        return proxyCreator;    }    /**     * 受权属性源参谋     */    @Bean    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();        advisor.setSecurityManager(securityManager);        return advisor;    }}
7、controller测试类
package com.walker.shiro.domain.controller;import com.sun.media.sound.FFT;import com.walker.shiro.domain.component.UserComponent;import com.walker.shiro.domain.model.common.R;import com.walker.shiro.domain.model.form.UserLoginForm;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.validation.Valid;/** * <p> * 用户表 前端控制器 * </p> * * @author walker * @since 2023-02-09 */@RestController@RequestMapping("/sys-user")public class SysUserController {    @Autowired    private UserComponent userComponent;    /**    * 登录接口    */    @PostMapping("/login")    public R login(@RequestBody @Valid UserLoginForm form){        return R.ok(userComponent.login(form));    }    /**    * 新增用户接口    */    @PostMapping("/add")    public R add(@RequestBody @Valid UserLoginForm form){        userComponent.add(form);        return R.ok();    }}
package com.walker.shiro.domain.controller;import com.walker.shiro.domain.model.common.R;import org.apache.shiro.authz.annotation.RequiresPermissions;import org.apache.shiro.authz.annotation.RequiresRoles;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/test")public class TestController {    @GetMapping("/get")    public R get(){        return R.ok("hello");    }    @GetMapping("/role_admin")    @RequiresRoles("admin")    public R role_admin(){        return R.ok("OK");    }    @GetMapping("/role_vip")    //角色判断    @RequiresRoles("vip")    public R role_vip(){        return R.ok("OK");    }    @GetMapping("/userAdd")    @RequiresPermissions("user:add")    public R userAdd(){        return R.ok("fail");    }    @GetMapping("/userList")    @RequiresPermissions("user:list")    public R userList(){        return R.ok("ok");    }}
8、应用postman进行测试
  • login接口

login接口因为是设置为白名单的,所以不须要Token,间接放行
能够发现可能获取到token,

之后将登录之后的token复制一下,用来其余接口的测试

  • get 接口,不带token时


能够发现,提醒须要token

  • get接口, 带token


当带上token时,就ok了

  • role_admin接口


该接口须要admin这个角色,而后目前咱们的admin账号刚好有该角色

发现是能够获取到后果的

  • role_vip接口



该接口须要vip角色,admin没有,所以后果提醒为没有权限

  • userAdd接口



因为admin只有user:list账号,所以不具备该权限

  • userList

调用胜利

总结

权限治理能够说在大部分的我的项目都是须要应用的了,shiro具体权限和认证的配置,是合乎咱们企业我的项目开发的,所以咱们还是得学习一下,心愿对你有所帮忙哈

本文由mdnice多平台公布