spring boot项目整合shiro,jwt实现权限控制(全网最详细哦!)

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

shiro介绍

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

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

 

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

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

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

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

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

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

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

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

Testing提供测试支持;

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

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

 

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

 

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

 

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

 

可以看到:应用代码直接交互的对象是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的架构,如下图所示:


  

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服务器);

SessionDAODAO大家都用过,数据访问对象,用于会话的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');

二、添加依赖

<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>1.6.0</version>
        </dependency>
 <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.10</version>
        </dependency>

    <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.4</version>
        </dependency>
   <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </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;

@Slf4j
public 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
 */
@Data
public 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
@Component
public 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
 */
@Slf4j
public 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<SysRole> roleList = this.roleService.findUserRole(username);
        Set<String> roleSet = roleList.stream().map(role -> role.getRoleName()).collect(Collectors.toSet());
        simpleAuthorizationInfo.setRoles(roleSet);

        // 获取用户权限集
        List<SysMenu> permissionList = this.menuService.findUserPermissions(username);
        Set<String> 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
 */
@Configuration
public 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<String, Filter> filters = new LinkedHashMap<>();
        filters.put("user", new JwtFilter());
        shiroFilterFactoryBean.setFilters(filters);

        LinkedHashMap<String, String> 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 页面的 <input type="checkbox" name="rememberMe"/>
        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
 *
 */
@Component
public 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> T getBean(Class<T> clazz){
		return applicationContext.getBean(clazz);
	}

	public static <T> T getBean(String name, Class<T> 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
@ServletComponentScan
public 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);
    }

}

项目 配置文件查看文末 gitee 仓库。

四、功能测试

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

    X-Access-Token :生成的token 

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

 五、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的访问权限。

配置缩写对应的过滤器功能
anonAnonymousFilter指定url可以匿名访问
authcFormAuthenticationFilter指定url需要form表单登录,默认会从请求中获取username、password,rememberMe等参数并尝试登录,如果登录不了就会跳转到loginUrl配置的路径。我们也可以用这个过滤器做默认的登录逻辑,但是一般都是我们自己在控制器写登录逻辑的,自己写的话出错返回的信息都可以定制嘛。
authcBasicBasicHttpAuthenticationFilter指定url需要basic登录
logoutLogoutFilter登出过滤器,配置指定url就可以实现退出功能,非常方便
noSessionCreationNoSessionCreationFilter禁止创建会话
permsPermissionsAuthorizationFilter需要指定权限才能访问
portPortFilter需要指定端口才能访问
restHttpMethodPermissionFilter将http请求方法转化成相应的动词来构造一个权限字符串,这个感觉意义不大,有兴趣自己看源码的注释
rolesRolesAuthorizationFilter需要指定角色才能访问
sslSslFilter需要https请求才能访问
userUserFilter需要已登录或“记住我”的用户才能访问

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

注解功能
@RequiresGuest只有游客可以访问
@RequiresAuthentication需要登录才能访问
@RequiresUser已登录的用户或“记住我”的用户能访问
@RequiresRoles已登录的用户需具有指定的角色才能访问
@RequiresPermissions已登录的用户需具有指定的权限才能访问
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页