共计 16660 个字符,预计需要花费 42 分钟才能阅读完成。
简介
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 的实现类
*/
@AllArgsConstructor
public 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
// 继承 AuthorizingRealm
public 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 配置
*/
@Configuration
public 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 多平台公布