,最近在忙着做权限控制项目,少有发布blog了,领导让我实现一个用户,角色,权限细粒度的权限控制功能集成到项目中,我经过了一定的调研之后,使用shiro,jwt实现了该功能,以此篇blog记之。

shiro介绍

Apache Shiro是Java的一个安全框架。目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。

本教程只介绍基本的Shiro使用,不会过多分析源码等,重在使用。

Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。这不就是我们想要的嘛,而且Shiro的API也是非常简单;其基本功能点如下图所示:

redisTemplate 设置时间一天 redis设置有效时间半小时_shiro分布式控制登录状态

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Web Support:Web支持,可以非常容易的集成到Web环境;

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

记住一点,Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。

接下来我们分别从外部和内部来看看Shiro的架构,对于一个好的框架,从外部来看应该具有非常简单易于使用的API,且API契约明确;从内部来看的话,其应该有一个可扩展的架构,即非常容易插入用户自定义实现,因为任何框架都不能满足所有需求。

首先,我们从外部来看Shiro吧,即从应用程序角度的来观察如何使用Shiro完成工作。如下图:

redisTemplate 设置时间一天 redis设置有效时间半小时_shiro分布式控制登录状态_02

 

可以看到:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个API的含义:

Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;

Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

也就是说对于我们而言,最简单的一个Shiro应用:

1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;

2、我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。

从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。

接下来我们来从Shiro内部来看下Shiro的架构,如下图所示:

redisTemplate 设置时间一天 redis设置有效时间半小时_缓存_03

Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;

SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。

Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;

SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);

SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;

CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。

一、开发前准备

(1)由上面的介绍我们可以知道shiro的Realm 可以进行自定义数据源,那是不是就会想到我们能否把用户,角色,权限信息存储到数据库中呢?答案是:肯定的。

在mysql 数据库中 执行 如下sql 脚本:

分别创建sys_menu:相当于权限表,

sys_role:角色表

sys_role_menu:角色权限关联表

sys_user: 用户表

sys_user_role: 用户角色关联表

----------------------- Table structure for sys_menu-- ----------------------------DROP TABLE IF EXISTS `sys_menu`;CREATE TABLE `sys_menu` (  `menu_id` bigint(20) NOT NULL AUTO_INCREMENT,  `parent_id` bigint(20) DEFAULT NULL,  `name` varchar(50) NOT NULL,  `route_name` varchar(255) DEFAULT NULL COMMENT '路由名称,用于前端跳转,唯一',  `url` varchar(100) DEFAULT NULL,  `perms` text,  `icon` varchar(50) DEFAULT NULL,  `type` tinyint(2) NOT NULL,  `order_num` int(11) DEFAULT NULL,  `component` varchar(200) DEFAULT NULL,  `component_name` varchar(500) DEFAULT NULL,  `redirect` varchar(200) DEFAULT NULL,  `iframe_url` varchar(1000) DEFAULT NULL COMMENT '外部链接,嵌入iframe',  `is_route` tinyint(2) DEFAULT '0',  `always_show` tinyint(2) DEFAULT '0',  `is_leaf` tinyint(2) DEFAULT '0',  `hidden` tinyint(2) DEFAULT '0',  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,  `create_by` varchar(50) DEFAULT NULL,  `update_time` datetime DEFAULT NULL,  `update_by` varchar(50) DEFAULT NULL,  PRIMARY KEY (`menu_id`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=58 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='菜单权限表'; -- ------------------------------ Records of sys_menu-- ----------------------------INSERT INTO `sys_menu` VALUES ('1', null, '系统管理', 'system', '/system', null, 'setting', '0', '99', 'layouts/TabLayout', null, '/system/user', null, '1', '0', '0', '0', null, null, '2020-05-14 16:11:35', 'caojun');INSERT INTO `sys_menu` VALUES ('2', '1', '用户管理', 'user', '/system/user', 'user:list', 'user', '1', '1', 'system/UserList', 'UserList', null, null, '1', '0', '1', '0', null, null, '2020-07-03 18:30:30', 'admin');INSERT INTO `sys_menu` VALUES ('3', '1', '角色管理', 'role', '/system/role', 'role:list', 'team', '1', '2', 'system/RoleList', 'RoleList', null, null, '1', '0', '1', '0', null, null, '2020-05-07 16:21:53', 'admin');INSERT INTO `sys_menu` VALUES ('4', '1', '菜单管理', 'permission', '/system/permission', 'menu:list', 'security-scan', '1', '3', 'system/PermissionList', 'PermissionList', null, null, '1', '0', '1', '0', null, null, '2020-05-07 16:21:47', 'admin');INSERT INTO `sys_menu` VALUES ('5', '1', '字典管理', 'dictionary', '/system/dictionary', null, 'read', '1', '4', 'system/DictionaryList', null, null, null, '1', '0', '1', '0', '2020-05-03 13:43:00', 'admin', '2020-05-07 16:22:06', 'admin');INSERT INTO `sys_menu` VALUES ('7', '1', '日志管理', 'log', '/system/log', null, 'book', '1', '5', 'system/LogList', null, null, null, '1', '0', '1', '0', '2020-05-05 10:00:00', 'admin', '2020-05-07 16:22:12', 'admin');INSERT INTO `sys_menu` VALUES ('8', null, '个人中心', 'personal', '/personal', null, 'user', '0', '2', 'layouts/TabLayout', null, '/personal/base', null, '1', '0', '0', '1', '2020-05-07 14:30:00', 'admin', '2020-05-14 13:59:55', 'caojun');INSERT INTO `sys_menu` VALUES ('9', '8', '基本信息', 'personalBase', '/personal/base', null, 'idcard', '1', '3', 'personal/base', null, null, null, '1', '0', '1', '0', '2020-05-07 14:35:00', 'admin', '2020-05-13 15:05:28', 'caojun');INSERT INTO `sys_menu` VALUES ('11', '8', '我的申请', 'personalApply', '/personal/apply', null, 'profile', '1', '2', 'personal/apply', null, null, null, '1', '0', '1', '0', '2020-05-13 14:55:02', 'admin', null, null);INSERT INTO `sys_menu` VALUES ('12', '8', '修改密码', 'personalPwd', '/personal/pwd', null, 'key', '1', '4', 'personal/pwd', null, null, null, '1', '0', '1', '0', '2020-05-13 14:58:00', 'admin', '2020-05-13 14:58:22', 'admin');INSERT INTO `sys_menu` VALUES ('36', null, '地图台', null, '/', null, 'global', '0', '1', 'layouts/MapLayout', null, '/home', null, '1', '0', '0', '0', '2020-05-22 15:17:00', 'admin', '2020-07-03 14:26:34', 'caojun');INSERT INTO `sys_menu` VALUES ('37', '36', '视频', null, '/home', null, null, '1', '1', 'video/index', null, null, null, '1', '0', '1', '0', '2020-05-22 15:18:00', 'admin', '2020-07-03 14:27:48', 'caojun');INSERT INTO `sys_menu` VALUES ('39', '36', '基站', null, '/station', null, null, '1', '4', 'station/index', null, null, null, '1', '0', '1', '0', '2020-06-02 13:39:00', 'admin', '2020-08-14 15:39:11', 'caojun');INSERT INTO `sys_menu` VALUES ('40', '36', '警力', null, '/police', null, null, '1', '5', 'police/index', null, null, null, '1', '0', '1', '0', '2020-06-02 13:41:00', 'admin', '2020-08-14 15:39:21', 'caojun');INSERT INTO `sys_menu` VALUES ('41', '36', '警车', null, '/car', null, null, '1', '6', 'car/index', null, null, null, '1', '0', '1', '0', '2020-06-02 13:43:00', 'admin', '2020-08-14 15:39:31', 'caojun');INSERT INTO `sys_menu` VALUES ('42', '36', '警情', null, '/alarm', null, '', '1', '3', 'layouts/RouteView', null, null, null, '1', '0', '0', '0', '2020-06-02 13:46:00', 'admin', '2020-08-14 15:39:02', 'admin');INSERT INTO `sys_menu` VALUES ('53', '36', '实战', null, '/training', null, null, '1', '7', 'training/index', null, null, null, '1', '0', '1', '0', '2020-07-03 14:25:00', 'caojun', '2020-08-11 11:34:19', null);INSERT INTO `sys_menu` VALUES ('54', '36', '人房', null, '/subject', null, null, '1', '2', 'subject/index', null, null, null, '1', '0', '1', '0', '2020-07-03 14:26:00', 'caojun', '2020-08-14 15:38:40', null);INSERT INTO `sys_menu` VALUES ('55', '42', '实时警情', null, '/alarm/policeCase', null, 'pic-center', '1', '1', 'alarm/policeCase/index', null, null, null, '1', '0', '1', '0', '2020-07-15 15:45:00', 'caojun', '2020-08-05 09:59:40', 'admin');INSERT INTO `sys_menu` VALUES ('56', '42', '警情热力图', null, '/alarm/heatMap', null, 'radar-chart', '1', '2', 'alarm/heatMap/index', null, null, null, '1', '0', '1', '0', '2020-07-17 16:52:00', 'admin', '2020-08-05 09:59:54', 'admin'); -- ------------------------------ Table structure for sys_role-- ----------------------------DROP TABLE IF EXISTS `sys_role`;CREATE TABLE `sys_role` (  `role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',  `role_key` varchar(100) NOT NULL COMMENT '角色唯一标识',  `role_name` varchar(100) DEFAULT NULL COMMENT '角色名称',  `remark` varchar(1000) DEFAULT NULL COMMENT '描述',  `create_time` datetime DEFAULT NULL COMMENT '创建时间',  `update_time` datetime DEFAULT NULL COMMENT '修改时间',  PRIMARY KEY (`role_id`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='系统角色表'; -- ------------------------------ Records of sys_role-- ----------------------------INSERT INTO `sys_role` VALUES ('1', 'SUPER_ADMIN', '超级管理员', null, null, '2020-07-17 18:01:25');INSERT INTO `sys_role` VALUES ('2', 'ADMIN', '管理员', '测试', null, '2020-07-22 09:53:37'); -- ------------------------------ Table structure for sys_role_menu-- ----------------------------DROP TABLE IF EXISTS `sys_role_menu`;CREATE TABLE `sys_role_menu` (  `uid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',  `role_key` varchar(100) NOT NULL COMMENT '角色标识',  `menu_id` bigint(20) NOT NULL COMMENT '菜单ID',  PRIMARY KEY (`uid`)) ENGINE=InnoDB AUTO_INCREMENT=784 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; -- ------------------------------ Records of sys_role_menu-- ----------------------------INSERT INTO `sys_role_menu` VALUES ('741', 'SUPER_ADMIN', '1');INSERT INTO `sys_role_menu` VALUES ('742', 'SUPER_ADMIN', '2');INSERT INTO `sys_role_menu` VALUES ('743', 'SUPER_ADMIN', '6');INSERT INTO `sys_role_menu` VALUES ('744', 'SUPER_ADMIN', '3');INSERT INTO `sys_role_menu` VALUES ('745', 'SUPER_ADMIN', '4');INSERT INTO `sys_role_menu` VALUES ('746', 'SUPER_ADMIN', '5');INSERT INTO `sys_role_menu` VALUES ('747', 'SUPER_ADMIN', '7');INSERT INTO `sys_role_menu` VALUES ('748', 'SUPER_ADMIN', '8');INSERT INTO `sys_role_menu` VALUES ('749', 'SUPER_ADMIN', '9');INSERT INTO `sys_role_menu` VALUES ('750', 'SUPER_ADMIN', '11');INSERT INTO `sys_role_menu` VALUES ('751', 'SUPER_ADMIN', '12');INSERT INTO `sys_role_menu` VALUES ('752', 'SUPER_ADMIN', '36');INSERT INTO `sys_role_menu` VALUES ('753', 'SUPER_ADMIN', '37');INSERT INTO `sys_role_menu` VALUES ('754', 'SUPER_ADMIN', '39');INSERT INTO `sys_role_menu` VALUES ('755', 'SUPER_ADMIN', '40');INSERT INTO `sys_role_menu` VALUES ('756', 'SUPER_ADMIN', '41');INSERT INTO `sys_role_menu` VALUES ('757', 'SUPER_ADMIN', '42');INSERT INTO `sys_role_menu` VALUES ('758', 'SUPER_ADMIN', '53');INSERT INTO `sys_role_menu` VALUES ('759', 'SUPER_ADMIN', '54');INSERT INTO `sys_role_menu` VALUES ('760', 'SUPER_ADMIN', '55');INSERT INTO `sys_role_menu` VALUES ('761', 'SUPER_ADMIN', '56');INSERT INTO `sys_role_menu` VALUES ('762', 'SUPER_ADMIN', '57');INSERT INTO `sys_role_menu` VALUES ('763', 'ADMIN', '6');INSERT INTO `sys_role_menu` VALUES ('764', 'ADMIN', '2');INSERT INTO `sys_role_menu` VALUES ('765', 'ADMIN', '1');INSERT INTO `sys_role_menu` VALUES ('766', 'ADMIN', '3');INSERT INTO `sys_role_menu` VALUES ('767', 'ADMIN', '4');INSERT INTO `sys_role_menu` VALUES ('768', 'ADMIN', '5');INSERT INTO `sys_role_menu` VALUES ('769', 'ADMIN', '7');INSERT INTO `sys_role_menu` VALUES ('770', 'ADMIN', '8');INSERT INTO `sys_role_menu` VALUES ('771', 'ADMIN', '9');INSERT INTO `sys_role_menu` VALUES ('772', 'ADMIN', '11');INSERT INTO `sys_role_menu` VALUES ('773', 'ADMIN', '12');INSERT INTO `sys_role_menu` VALUES ('774', 'ADMIN', '36');INSERT INTO `sys_role_menu` VALUES ('775', 'ADMIN', '37');INSERT INTO `sys_role_menu` VALUES ('776', 'ADMIN', '39');INSERT INTO `sys_role_menu` VALUES ('777', 'ADMIN', '40');INSERT INTO `sys_role_menu` VALUES ('778', 'ADMIN', '41');INSERT INTO `sys_role_menu` VALUES ('779', 'ADMIN', '42');INSERT INTO `sys_role_menu` VALUES ('780', 'ADMIN', '53');INSERT INTO `sys_role_menu` VALUES ('781', 'ADMIN', '54');INSERT INTO `sys_role_menu` VALUES ('782', 'ADMIN', '55');INSERT INTO `sys_role_menu` VALUES ('783', 'ADMIN', '56'); -- ------------------------------ Table structure for sys_user-- ----------------------------DROP TABLE IF EXISTS `sys_user`;CREATE TABLE `sys_user` (  `user_id` bigint(20) NOT NULL AUTO_INCREMENT,  `username` varchar(50) NOT NULL COMMENT '账号',  `password` varchar(128) NOT NULL COMMENT '密码',  `name` varchar(200) DEFAULT NULL COMMENT '正式姓名',  `dept_id` int(11) DEFAULT NULL COMMENT '部门ID',  `email` varchar(128) DEFAULT NULL COMMENT '邮箱',  `mobile` varchar(20) DEFAULT NULL COMMENT '手机号',  `ssex` tinyint(1) DEFAULT NULL COMMENT '性别',  `avatar` varchar(100) DEFAULT NULL,  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,  `update_time` datetime DEFAULT NULL,  `last_login_time` datetime DEFAULT NULL,  `theme` varchar(10) DEFAULT NULL,  `status` tinyint(1) NOT NULL COMMENT '状态',  `description` varchar(100) DEFAULT NULL,  `adcode` varchar(10) DEFAULT NULL COMMENT '城市编码',  `region_id` varchar(32) DEFAULT NULL,  PRIMARY KEY (`user_id`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='系统用户表'; -- ------------------------------ Records of sys_user-- ----------------------------INSERT INTO `sys_user` VALUES ('1', 'admin', '123456', '超管', '1', 'admin@sfmail.com', '13455533222', '1', null, '2019-05-06 14:31:37', '2020-06-08 11:09:44', '2019-05-07 10:11:23', 'indigo', '1', '我是管理员', '361100', '3611');INSERT INTO `sys_user` VALUES ('3', 'caojun', '3d9f549610dc8eaef5ebe305b7a31f0b', '曹军', null, 'caojunior@163.com', null, '1', 'default.jpg', '2020-05-02 11:11:00', '2020-05-13 15:04:43', null, 'green', '1', null, null, null);INSERT INTO `sys_user` VALUES ('5', 'test123', '8e7dc6b8522024ee33db550c7f14d671', 'scott', null, null, null, null, 'default.jpg', '2020-05-02 14:32:52', null, null, 'green', '1', null, null, null);INSERT INTO `sys_user` VALUES ('6', 'yingang', '23ead6f6bd37eaf3e70d76c171dddd91', '殷刚', null, null, null, '1', 'default.jpg', '2020-05-23 16:13:41', null, null, 'green', '1', null, null, null);INSERT INTO `sys_user` VALUES ('7', 'chenjing', 'a31804c9ef2aeac31388f2da1235766d', '陈景', null, null, null, '1', 'default.jpg', '2020-06-28 16:31:34', null, null, 'green', '1', null, null, null);INSERT INTO `sys_user` VALUES ('8', '123yhf', '794b1a4bd91606df7396a0ff7a570ad0', 'yhf', null, null, null, '1', 'default.jpg', '2020-07-31 16:10:52', null, null, 'green', '1', null, null, null); -- ------------------------------ Table structure for sys_user_role-- ----------------------------DROP TABLE IF EXISTS `sys_user_role`;CREATE TABLE `sys_user_role` (  `uid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',  `username` varchar(100) NOT NULL COMMENT '账号',  `role_key` varchar(100) NOT NULL COMMENT '角色标识',  PRIMARY KEY (`uid`)) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='用户角色关系表'; -- ------------------------------ Records of sys_user_role-- ----------------------------INSERT INTO `sys_user_role` VALUES ('1', 'admin', 'SUPER_ADMIN');INSERT INTO `sys_user_role` VALUES ('4', 'caojun', 'ADMIN');INSERT INTO `sys_user_role` VALUES ('6', 'yingang', 'SUPER_ADMIN');INSERT INTO `sys_user_role` VALUES ('7', 'chenjing', 'ADMIN');INSERT INTO `sys_user_role` VALUES ('8', '123yhf', 'ADMIN');

二、添加依赖

<dependency>            <groupId>com.auth0groupId>            <artifactId>java-jwtartifactId>            <version>3.10.3version>        dependency>                 <dependency>            <groupId>org.apache.shirogroupId>            <artifactId>shiro-spring-boot-web-starterartifactId>            <version>1.6.0version>        dependency> <dependency>            <groupId>cn.hutoolgroupId>            <artifactId>hutool-allartifactId>            <version>5.3.10version>        dependency>     <dependency>            <groupId>com.google.code.gsongroupId>            <artifactId>gsonartifactId>            <version>2.8.4version>        dependency>   <dependency>            <groupId>org.projectlombokgroupId>            <artifactId>lombokartifactId>            <optional>trueoptional>        dependency>

三、代码编写

JwtTokenController :模拟登陆获取token的接口,大家可以根据实际项目需求,采用真实的登陆接口,如果登陆成功,则使用用户的信息生成token,返回给前端,前端在调用接口的时候需要在请求头header中加入参数 

X-Access-Token:生成的token

package com.sf.gis.boot.rcboot.controller; import com.sf.gis.boot.rcboot.shiro.JWTUtil;import com.sf.gis.boot.rcboot.util.JsonResponse;import io.swagger.annotations.Api;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController; /** * @author 80004819 * @ClassName: * @Description: * @date 2020年09月11日 17:38:09 */@RestController@RequestMapping("/jwt")@Slf4j@Api(tags = "JWT权限controller")public class JwtTokenController {      /**     * 模拟登陆接口获取到token,有效期为30分钟     *     * @return     */    @GetMapping("/getToken")    public JsonResponse getToken() {        try {            //String token = JWTUtil.createToken(new User("admin", "123456"));            String token = JWTUtil.sign("admin", "123456", false);            return JsonResponse.ok(JsonResponse.STATUS_SUCCESS, token);        } catch (Exception e) {            log.error("error", e);            return JsonResponse.error("获取token失败");        }    } }

JWTUtil :创建和校验token工具类

此处可以自定义一个秘钥和用户的信息一起进行生成token,并设置一个过期时间,此处的rememberMe实际上就是过期时间设置的特别长,如果不适用rememberMe ,则默认过期时间是30分钟(半小时)。

注意:在生成token 和 验证token 的时候,放入的claim中的数据体一定要保持一致,否则会校验失败。

package com.sf.gis.boot.rcboot.shiro; import com.auth0.jwt.JWT;import com.auth0.jwt.JWTVerifier;import com.auth0.jwt.algorithms.Algorithm;import com.sf.gis.boot.rcboot.constants.Common;import com.sf.gis.boot.rcboot.shiro.entity.SysUser;import com.sf.gis.boot.rcboot.util.SpringContextUtils;import lombok.extern.slf4j.Slf4j; import java.util.Date; @Slf4jpublic class JWTUtil {      public static final long EXPIRE_TIME = SpringContextUtils.getBean(ShiroProperties.class).getJwtTimeOut() * 1000;    public static final String MY_SECRET = "my_secret";     /**     * 生成 token     *     * @param username 用户名     * @return token     */    public static String sign(String username,String password,Boolean rememberMe) {         long expireTime = null != rememberMe && rememberMe ? EXPIRE_TIME : 30 * 60 * 1000 ;        try {             Date date = new Date(System.currentTimeMillis() + expireTime);            Algorithm algorithm = Algorithm.HMAC256(MY_SECRET+password);            String userinfo = Common.GSON.toJson(new SysUser().setUsername(username).setPassword(password));            return JWT.create()                    .withClaim("userinfo", userinfo)                    .withExpiresAt(date)                    .sign(algorithm);        } catch (Exception e) {            log.error("error:{}", e);            return null;        }    }     /**     * 校验 token是否正确     *     * @param token  密钥     * @return 是否正确     */    public static boolean verify(String token,String username,String password) {        try {             Algorithm algorithm = Algorithm.HMAC256(MY_SECRET+password);            String userinfo = Common.GSON.toJson(new SysUser().setUsername(username).setPassword(password));            JWTVerifier verifier = JWT.require(algorithm)                    .withClaim("userinfo", userinfo)                    .build();            verifier.verify(token);//            log.info("token is valid");            return true;        } catch (Exception e) {            log.info("token is invalid{}", e.getMessage());            return false;        }    } }

JWTToken  :实现AuthenticationToken 接口,shiro 在获取subject之后的login操作需要自定义一个token对象传入

package com.sf.gis.boot.rcboot.shiro; import lombok.Data;import org.apache.shiro.authc.AuthenticationToken; /** * JSON Web Token */@Datapublic class JWTToken implements AuthenticationToken {     private static final long serialVersionUID = 1282057025599826155L;     private String token;     private String exipreAt;     public JWTToken(String token) {        this.token = token;    }     public JWTToken(String token, String exipreAt) {        this.token = token;        this.exipreAt = exipreAt;    }     @Override    public Object getPrincipal() {        return token;    }     @Override    public Object getCredentials() {        return token;    } }

JWTFilter:继承BasicHttpAuthenticationFilter 类,添加@WebFilter注解,同时在 RcBootApplication 项目启动类添加

@ServletComponentScan 注解,会自动扫描 过滤器将之注册到spring容器中。

package com.sf.gis.boot.rcboot.shiro; import com.google.gson.Gson;import com.sf.gis.boot.rcboot.util.JsonResponse;import com.sf.gis.boot.rcboot.util.SpringContextUtils;import lombok.extern.slf4j.Slf4j;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;import org.springframework.http.HttpStatus;import org.springframework.web.bind.annotation.RequestMethod; import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.annotation.WebFilter;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException; /** * @author 80004819 * @ClassName: * @Description: * @date 2020年09月11日 17:34:49 */@Slf4j@WebFilter(filterName = "JwtFilter", urlPatterns = "/*")public class JwtFilter extends BasicHttpAuthenticationFilter {     private static final Gson GSON = new Gson();     private static final String TOKEN = "X-Access-Token";      /**     * 所有请求直接走login     *     * @param request     * @param response     * @param mappedValue     * @return     */    @Override    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {        try {            return executeLogin(request, response);        } catch (Exception e) {            throw new AuthenticationException("Token失效,请重新登录", e);        }    }     /**     * 判断是否是登陆请求     *     * @param request     * @param response     * @return     */    @Override    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {        HttpServletRequest req = (HttpServletRequest) request;        String token = req.getHeader(TOKEN);        String referer = req.getHeader("Referer");        if (token == null) {            try {                //如果响应还没有被提交,重定向到/                if (!response.isCommitted()) {                    request.getRequestDispatcher("/").forward(request, response);//                    WebUtils.issueRedirect(request, response, "/");                }            } catch (IOException e) {                e.printStackTrace();            } catch (ServletException e) {                e.printStackTrace();            }            return false;        }        return true;    }    /**    * 执行登陆校验    */    @Override    protected boolean executeLogin(ServletRequest request, ServletResponse response) {        HttpServletRequest httpServletRequest = (HttpServletRequest) request;        String token = httpServletRequest.getHeader(TOKEN);        JWTToken jwtToken = new JWTToken(token);        try {            getSubject(request, response).login(jwtToken);            return true;        } catch (Exception e) {            log.error(e.getMessage());            return false;        }    }     /**      *如果校验失败会执行此方法      */    @Override    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {        ShiroProperties geoProperties = SpringContextUtils.getBean(ShiroProperties.class);        String loginUrl = geoProperties.getLoginUrl();         HttpServletRequest request = (HttpServletRequest) servletRequest;        HttpServletResponse response = (HttpServletResponse) servletResponse;        response.setCharacterEncoding("UTF-8");        response.setContentType("application/json; charset=utf-8");        response.setStatus(HttpStatus.UNAUTHORIZED.value());        if (response.isCommitted()) {            return false;        }//        WebUtils.issueRedirect(request, response, loginUrl);        response.getWriter().write(GSON.toJson(JsonResponse.error("token校验不通过")));        return false;    }      /**     * 对跨域提供支持     */    @Override    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {        HttpServletRequest httpServletRequest = (HttpServletRequest) request;        HttpServletResponse httpServletResponse = (HttpServletResponse) response;        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));        // 跨域时会首先发送一个 option请求,这里我们给 option请求直接返回正常状态        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {            httpServletResponse.setStatus(HttpStatus.OK.value());            return false;        }        return super.preHandle(request, response);    }  }

ShiroProperties :ShiroFilterFactoryBean 相关常量配置

package com.sf.gis.boot.rcboot.shiro; import lombok.Data;import org.springframework.stereotype.Component; @Data@Componentpublic class ShiroProperties {    // shiro redis缓存时长,默认值 1800 秒    private int expireIn = 1800;    // session 超时时间,默认 1800000毫秒    private long sessionTimeout = 1800000L;    // rememberMe 有效时长,默认为 86400 秒,即一天    private int cookieTimeout = 86400;     private String anonUrl;     private String loginUrl = "/login";     private String successUrl = "/index";     private String logoutUrl = "/logout";     private String unauthorizedUrl;     /**     * token默认有效时间 1天     */    private Long jwtTimeOut = 86400L;  }

ShiroUtil :shrio工具类

package com.sf.gis.boot.rcboot.shiro; import cn.hutool.core.util.StrUtil;import com.auth0.jwt.JWT;import com.auth0.jwt.exceptions.JWTDecodeException;import com.auth0.jwt.interfaces.DecodedJWT;import com.sf.gis.boot.rcboot.constants.Common;import com.sf.gis.boot.rcboot.shiro.entity.SysUser;import lombok.extern.slf4j.Slf4j;import org.apache.shiro.SecurityUtils;import org.apache.shiro.session.Session;import org.apache.shiro.subject.Subject; /** * @author 80004819 * @ClassName: * @Description: * @date 2020年09月14日 15:10:11 */@Slf4jpublic class ShiroUtil {      /**     * 获取subject     *     * @return     */    public static Subject getSubject() {        try {            Subject subject = SecurityUtils.getSubject();            return subject;        } catch (Exception e) {            log.error("未登录", e);            return null;        }    }  /**     * 获取session     *     * @return     */    public static Session getSession() {        return getSubject().getSession();    }   /**     * 获取session     *     * @return     */    protected Session getSession(Boolean flag) {        return getSubject().getSession(flag);    }      /**     * 从 token中获取用户名     *     * @return token中包含的用户名     */    public static String getUsername() {        return getUsername(null);    }     public static String getUsername(String token) {        SysUser sysUser = getUserInfo(token);        return StrUtil.isNotBlank(sysUser.getUsername()) ? sysUser.getUsername() : null;    }     /**     * 将token解密成SysUser对象     *     * @param token     * @return     */    public static SysUser getUserInfo(String token) {        if (StrUtil.isEmpty(token)) {            if (null == getSubject() || null == getSubject().getPrincipal()) return null;            token = getSubject().getPrincipal().toString();        }        try {            DecodedJWT jwt = JWT.decode(token);            String userinfo = jwt.getClaim("userinfo").asString();            // JsonObject info = Common.GSON.fromJson(userinfo, JsonObject.class);            SysUser sysUser = Common.GSON.fromJson(userinfo, SysUser.class);            return sysUser;        } catch (JWTDecodeException e) {            log.error("error:{}", e.getMessage());            return null;        }    }  }

ShiroRealm :自定义Realm

  • 重写  doGetAuthorizationInfo方法:授权方法,获取用户的角色和权限
  • 重写  doGetAuthenticationInfo方法:认证方法,验证用户身份,用户名密码,token是否正确
package com.sf.gis.boot.rcboot.shiro; import cn.hutool.core.util.StrUtil;import com.sf.gis.boot.rcboot.shiro.entity.SysMenu;import com.sf.gis.boot.rcboot.shiro.entity.SysRole;import com.sf.gis.boot.rcboot.shiro.entity.SysUser;import com.sf.gis.boot.rcboot.shiro.service.SysMenuService;import com.sf.gis.boot.rcboot.shiro.service.SysRoleService;import com.sf.gis.boot.rcboot.shiro.service.SysUserService;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 java.util.Arrays;import java.util.HashSet;import java.util.List;import java.util.Set;import java.util.stream.Collectors; /** * @author 80004819 * @ClassName: * @Description: * @date 2020年09月14日 15:33:22 */public class ShiroRealm extends AuthorizingRealm {     @Autowired    private SysUserService userService;    @Autowired    private SysRoleService roleService;    @Autowired    private SysMenuService menuService;      @Override    public boolean supports(AuthenticationToken token) {        return token instanceof JWTToken;    }      /**     * 授权模块,获取用户角色和权限     *     * @param principal principal     * @return AuthorizationInfo 权限信息     */    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {//        User user = (User) SecurityUtils.getSubject().getPrincipal();//        String userName = user.getUsername();        String username = ShiroUtil.getUsername(principal.toString());         SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();         // 获取用户角色集        List roleList = this.roleService.findUserRole(username);        Set roleSet = roleList.stream().map(role -> role.getRoleName()).collect(Collectors.toSet());        simpleAuthorizationInfo.setRoles(roleSet);         // 获取用户权限集        List permissionList = this.menuService.findUserPermissions(username);        Set permissionSet = new HashSet<>();        for (SysMenu m : permissionList) {            // 处理用户多权限 用逗号分隔            permissionSet.addAll(Arrays.asList(m.getPerms().split(",")));        }        simpleAuthorizationInfo.setStringPermissions(permissionSet);        return simpleAuthorizationInfo;    }     /**     * 用户认证     *     * @param authenticationToken AuthenticationToken 身份认证 token     * @return AuthenticationInfo 身份认证信息     * @throws AuthenticationException 认证相关异常     */    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {         // 获取用户输入的用户名和密码//        String userName = (String) token.getPrincipal();//        String password = new String((char[]) token.getCredentials());////        if (User.STATUS_LOCK.equals(user.getStatus())) {//            throw new LockedAccountException("账号已被锁定,请联系管理员!");//        }         String token = (String) authenticationToken.getCredentials();        SysUser sysUser = ShiroUtil.getUserInfo(token);        String username = sysUser.getUsername();        String password = sysUser.getPassword();         if (StrUtil.isBlank(username))            throw new AuthenticationException("账号为空,token校验不通过");         // 通过用户名查询用户信息        SysUser user = this.userService.findByNamePassword(username,password);         if (user == null)            throw new AuthenticationException("用户名或密码错误"); //        JsonObject jo = new JsonObject();//        jo.addProperty("username", username);//        jo.addProperty("userid", user.getUserId());         if (!JWTUtil.verify(token, username, password))            throw new AuthenticationException("token校验不通过");        // 判断用户状态        if (null == user.getStatus() || user.getStatus() != 1) {            throw new AuthenticationException("账号已被冻结,请联系管理员!");        }        return new SimpleAuthenticationInfo(token, token, getName());    } }

ShiroConfig

package com.sf.gis.boot.rcboot.shiro; import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;import org.apache.shiro.mgt.DefaultSubjectDAO;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.spring.LifecycleBeanPostProcessor;import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.CookieRememberMeManager;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.apache.shiro.web.servlet.SimpleCookie;import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.DependsOn; import javax.servlet.Filter;import java.util.LinkedHashMap; /** * @author 80004819 * @ClassName: * @Description: * @date 2020年09月14日 15:21:28 */@Configurationpublic class ShiroConfig {      @Autowired    private ShiroProperties shiroProperties;     //配置文件中配置项:用于设置是否开启对url的权限验证    @Value("${jwt.tokenAuth.enable}")    private boolean tokenAuth;     @Bean("shiroFilterFactoryBean")    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();        //设置安全管理器        shiroFilterFactoryBean.setSecurityManager(securityManager);        // 登录的 url        shiroFilterFactoryBean.setLoginUrl(shiroProperties.getLoginUrl());        // 登录成功后跳转的 url        shiroFilterFactoryBean.setSuccessUrl(shiroProperties.getSuccessUrl());        // 未授权 url        shiroFilterFactoryBean.setUnauthorizedUrl(shiroProperties.getUnauthorizedUrl());        // 在 Shiro过滤器链上加入 JWTFilter        LinkedHashMap filters = new LinkedHashMap<>();        filters.put("user", new JwtFilter());        shiroFilterFactoryBean.setFilters(filters);         LinkedHashMap filterChainDefinitionMap = new LinkedHashMap<>();        // 配置退出过滤器,其中具体的退出代码 Shiro已经替我们实现了        filterChainDefinitionMap.put(shiroProperties.getLogoutUrl(), "logout");        //配置免认证的url,url可以从配置文件读取        filterChainDefinitionMap.put("/about", "anon");        filterChainDefinitionMap.put("/jwt/getToken", "anon");        filterChainDefinitionMap.put("/", "anon");        filterChainDefinitionMap.put("/swagger-ui.html", "anon");        filterChainDefinitionMap.put("/error", "anon");        filterChainDefinitionMap.put("/csrf", "anon");        filterChainDefinitionMap.put("/v2/api-docs/**", "anon");        filterChainDefinitionMap.put("/swagger-resources/**", "anon");         if (tokenAuth) {            //除上以外所有url都必须认证通过才可以访问,未通过认证自动访问 LoginUrl            filterChainDefinitionMap.put("/**", "user");        } else {            //所有url都可以直接匿名访问            filterChainDefinitionMap.put("/**", "anon");        }         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);         return shiroFilterFactoryBean;     }     // 配置url过滤器//    @Bean//    public ShiroFilterChainDefinition shiroFilterChainDefinition() {//        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();////        chainDefinition.addPathDefinition(shiroProperties.getLogoutUrl(), "logout");//        //配置免认证的url,url可以从配置文件读取//        chainDefinition.addPathDefinition("/login", "anon");//        chainDefinition.addPathDefinition("/about", "anon");//        chainDefinition.addPathDefinition("/jwt/getToken", "anon");//        chainDefinition.addPathDefinition("/", "anon");//        chainDefinition.addPathDefinition("/swagger-ui.html", "anon");//        chainDefinition.addPathDefinition("/error", "anon");//        chainDefinition.addPathDefinition("/csrf", "anon");//        chainDefinition.addPathDefinition("/v2/api-docs/**", "anon");//        chainDefinition.addPathDefinition("/swagger-resources/**", "anon");//        //除上以外所有url都必须认证通过才可以访问,未通过认证自动访问 LoginUrl//        chainDefinition.addPathDefinition("/**", "user");//        return chainDefinition;//    }      @Bean("securityManager")    public DefaultWebSecurityManager securityManager() {        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();        // 配置 SecurityManager,并注入 shiroRealm        securityManager.setRealm(shiroRealm());        // 配置 rememberMeCookie        securityManager.setRememberMeManager(rememberMeManager());//        securityManager.setSessionManager(sessionManager());         // 关闭shiro自带的session        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);        securityManager.setSubjectDAO(subjectDAO);         return securityManager;    }     @Bean(name = "lifecycleBeanPostProcessor")    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {        // shiro 生命周期处理器        return new LifecycleBeanPostProcessor();    }     @Bean    public ShiroRealm shiroRealm() {        // 配置 Realm,需自己实现        return new ShiroRealm();    }     /**     * rememberMe cookie 效果是重开浏览器后无需重新登录     *     * @return SimpleCookie     */    private SimpleCookie rememberMeCookie() {        // 设置 cookie 名称,对应 login.html 页面的         SimpleCookie cookie = new SimpleCookie("rememberMe");//        cookie.setSecure(true);  // 只在 https中有效 注释掉 正常        // 设置 cookie 的过期时间,单位为秒,这里为一天        cookie.setMaxAge(shiroProperties.getCookieTimeout());        return cookie;    }     /**     * cookie管理对象     *     * @return CookieRememberMeManager     */    private CookieRememberMeManager rememberMeManager() {        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();        cookieRememberMeManager.setCookie(rememberMeCookie());        // rememberMe cookie 加密的密钥        //cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));        cookieRememberMeManager.setCipherKey(new AesCipherService().generateNewKey().getEncoded());        return cookieRememberMeManager;    }     /**     * DefaultAdvisorAutoProxyCreator 和 AuthorizationAttributeSourceAdvisor 用于开启 shiro 注解的使用     * 如 @RequiresAuthentication, @RequiresUser, @RequiresPermissions 等     *     * @return DefaultAdvisorAutoProxyCreator     */    @Bean    @DependsOn({"lifecycleBeanPostProcessor"})    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();        advisorAutoProxyCreator.setProxyTargetClass(true);        return advisorAutoProxyCreator;    }     @Bean    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);        return authorizationAttributeSourceAdvisor;    }  }

SpringContextUtils

package com.sf.gis.boot.rcboot.util; import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; /** * Spring Context 工具类 *  * @author MrBird * */@Componentpublic class SpringContextUtils implements ApplicationContextAware {  private static ApplicationContext applicationContext;   @Override  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {    SpringContextUtils.applicationContext = applicationContext;  }   public static Object getBean(String name) {    return applicationContext.getBean(name);  }  public static T getBean(Class clazz){    return applicationContext.getBean(clazz);  }   public static T getBean(String name, Class requiredType) {    return applicationContext.getBean(name, requiredType);  }   public static boolean containsBean(String name) {    return applicationContext.containsBean(name);  }   public static boolean isSingleton(String name) {    return applicationContext.isSingleton(name);  }   public static Class> getType(String name) {    return applicationContext.getType(name);  }   /**   * 获取HttpServletRequest   */  public static HttpServletRequest getHttpServletRequest() {    return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();  }}

RcBootApplication

package com.sf.gis.boot.rcboot; import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.builder.SpringApplicationBuilder;import org.springframework.boot.web.servlet.ServletComponentScan;import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;import org.springframework.scheduling.annotation.EnableScheduling;import org.springframework.transaction.annotation.EnableTransactionManagement;import springfox.documentation.swagger2.annotations.EnableSwagger2; //@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class})@SpringBootApplication@MapperScan(basePackages = {"com.sf.gis.boot.rcboot.mapper","com.sf.gis.boot.rcboot.shiro.mapper"})@EnableSwagger2@EnableScheduling@EnableTransactionManagement@ServletComponentScanpublic class RcBootApplication  extends SpringBootServletInitializer {     @Override    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {        return builder.sources(RcBootApplication.class);    }     public static void main(String[] args) {        SpringApplication.run(RcBootApplication.class, args);    } }

四、功能测试

  1.    首先,我们不调用获取token接口,直接请求任一业务接口,发现会返回token验证不通过的信息
  2.  然后,我们调用getToken接口,获取到token,并在下次请求任一业务接口的时候在请求头header中添加参数

X-Access-Token :生成的token

         发现可以认证成功,并且接口请求成功返回想要的数据信息。

redisTemplate 设置时间一天 redis设置有效时间半小时_缓存_04

redisTemplate 设置时间一天 redis设置有效时间半小时_Web_05

五、shiro注解式权限控制

Shiro共有5个注解,可以在controller接口层添加这些注解实现更加细粒度的权限控制

  • RequiresAuthentication:

使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证。

  • RequiresGuest:

使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是“gust”身份,不需要经过认证或者在原先的session中存在记录。

  • RequiresPermissions:

当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。

  • RequiresRoles:

当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。

  • RequiresUser

当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。


使用方法:

Shiro的认证注解处理是有内定的处理顺序的,如果有个多个注解的话,前面的通过了会继续检查后面的,若不通过则直接返回,处理顺序依次为(与实际声明顺序无关):

RequiresRoles 
RequiresPermissions 
RequiresAuthentication 
RequiresUser 
RequiresGuest

示例:

RequiresRoles

//属于user角色@RequiresRoles("user") //必须同时属于user和admin角色@RequiresRoles({"user","admin"}) //属于user或者admin之一;修改logical为OR 即可@RequiresRoles(value={"user","admin"},logical=Logical.OR)

 RequiresPermissions

//符合index:hello权限要求@RequiresPermissions("index:hello") //必须同时复核index:hello和index:world权限要求@RequiresPermissions({"index:hello","index:world"}) //符合index:hello或index:world权限要求即可@RequiresPermissions(value={"index:hello","index:world"},logical=Logical.OR)

附录:

shiro提供和多个默认的过滤器,我们可以用这些过滤器来配置过滤指定url的访问权限。

配置缩写

对应的过滤器

功能

anon

AnonymousFilter

指定url可以匿名访问

authc

FormAuthenticationFilter

指定url需要form表单登录,默认会从请求中获取username、password,rememberMe等参数并尝试登录,如果登录不了就会跳转到loginUrl配置的路径。我们也可以用这个过滤器做默认的登录逻辑,但是一般都是我们自己在控制器写登录逻辑的,自己写的话出错返回的信息都可以定制嘛。

authcBasic

BasicHttpAuthenticationFilter

指定url需要basic登录

logout

LogoutFilter

登出过滤器,配置指定url就可以实现退出功能,非常方便

noSessionCreation

NoSessionCreationFilter

禁止创建会话

perms

PermissionsAuthorizationFilter

需要指定权限才能访问

port

PortFilter

需要指定端口才能访问

rest

HttpMethodPermissionFilter

将http请求方法转化成相应的动词来构造一个权限字符串,这个感觉意义不大,有兴趣自己看源码的注释

roles

RolesAuthorizationFilter

需要指定角色才能访问

ssl

SslFilter

需要https请求才能访问

user

UserFilter

需要已登录或“记住我”的用户才能访问

shiro常用的权限控制注解,可以在控制器类上使用

注解

功能

@RequiresGuest

只有游客可以访问

@RequiresAuthentication

需要登录才能访问

@RequiresUser

已登录的用户或“记住我”的用户能访问

@RequiresRoles

已登录的用户需具有指定的角色才能访问

@RequiresPermissions

已登录的用户需具有指定的权限才能访问