前言
关于 Java 安全框架,一个是Spring Security,一个是Shiro。这两个框架都是很不错的,没有绝对谁好谁坏,看业务场景选择框架,最适合的才是最好的。
今天我们主要来聊聊Shiro这个安全框架,我相信你们也是经常用到。至于为什么会有很多人选择使用Shiro,我认为在众多权限框架中,Shiro因其简单而又不失强大的特点引起了不少开发者的注意。
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API。您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
总结一点,就是因为Shiro简单、灵活、易上手。至于那些基本概念,我觉得也很简单,在代码中一步一步来进行说明。
正文
shiro 实现登录、认证、授权的流程大概如下:
springboot集成Shiro框架实现按钮级别的权限。涉及权限,这里面就涉及到用户、角色、权限三张表和用户角色、角色权限两张关联表。数据库我用的是常见的MYSQL,这里我简单设计了一下表的结构,如下。
DROPTABLEIFEXISTS`sys_permission`;CREATETABLE`sys_permission`(`id`int(11)NOTNULL,`available`int(11)DEFAULTNULL,`name`varchar(255)DEFAULTNULL,`parent_id`int(11)DEFAULTNULL,`parent_ids`varchar(255)DEFAULTNULL,`permission`varchar(255)DEFAULTNULL,`resource_type`varchar(255)DEFAULTNULL,`url`varchar(255)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8;--------------------------------Recordsofsys_permission------------------------------INSERTINTO`sys_permission`VALUES('1','0','用户管理','0','0/','userInfo:view','menu','userInfo/userList');INSERTINTO`sys_permission`VALUES('2','0','用户添加','1','0/1','userInfo:add','button','userInfo/userAdd');INSERTINTO`sys_permission`VALUES('3','0','用户删除','1','0/1','userInfo:del','button','userInfo/userDel');--------------------------------Tablestructureforsys_role------------------------------DROPTABLEIFEXISTS`sys_role`;CREATETABLE`sys_role`(`id`int(11)NOTNULL,`available`int(11)DEFAULTNULL,`description`varchar(255)DEFAULTNULL,`role`varchar(255)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8;--------------------------------Recordsofsys_role------------------------------INSERTINTO`sys_role`VALUES('1','0','管理员','admin');INSERTINTO`sys_role`VALUES('2','0','VIP会员','vip');INSERTINTO`sys_role`VALUES('3','1','测试人员','test');--------------------------------Tablestructureforsys_role_permission------------------------------DROPTABLEIFEXISTS`sys_role_permission`;CREATETABLE`sys_role_permission`(`permission_id`int(11)DEFAULTNULL,`role_id`int(11)DEFAULTNULL,KEY`FKomxrs8a388bknvhjokh440waq`(`permission_id`),KEY`FK9q28ewrhntqeipl1t04kh1be7`(`role_id`),CONSTRAINT`FK9q28ewrhntqeipl1t04kh1be7`FOREIGNKEY(`role_id`)REFERENCES`sys_role`(`id`),CONSTRAINT`FKomxrs8a388bknvhjokh440waq`FOREIGNKEY(`permission_id`)REFERENCES`sys_permission`(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8;--------------------------------Recordsofsys_role_permission------------------------------INSERTINTO`sys_role_permission`VALUES('1','1');INSERTINTO`sys_role_permission`VALUES('2','2');INSERTINTO`sys_role_permission`VALUES('3','2');INSERTINTO`sys_role_permission`VALUES('2','3');--------------------------------Tablestructureforsys_user------------------------------DROPTABLEIFEXISTS`sys_user`;CREATETABLE`sys_user`(`uid`int(11)NOTNULL,`username`varchar(255)DEFAULTNULL,`name`varchar(255)DEFAULTNULL,`password`varchar(255)DEFAULTNULL,`salt`varchar(255)DEFAULTNULL,`state`int(1)DEFAULTNULL,PRIMARYKEY(`uid`))ENGINE=InnoDBDEFAULTCHARSET=utf8;--------------------------------Recordsofsys_user------------------------------INSERTINTO`sys_user`VALUES('1','admin','管理员','123456','8d78869f470951332959580424d4bf4f','0');INSERTINTO`sys_user`VALUES('2','jiangwang','vip','123456','8d78869f470951332959580424d4bf4f','0');INSERTINTO`sys_user`VALUES('3','test','测试','123456','8d78869f470951332959580424d4bf4f','0');--------------------------------Tablestructureforsys_user_role------------------------------DROPTABLEIFEXISTS`sys_user_role`;CREATETABLE`sys_user_role`(`role_id`int(11)DEFAULTNULL,`uid`int(11)DEFAULTNULL,KEY`FKhh52n8vd4ny9ff4x9fb8v65qx`(`role_id`),KEY`FKgkmyslkrfeyn9ukmolvek8b8f`(`uid`),CONSTRAINT`FKgkmyslkrfeyn9ukmolvek8b8f`FOREIGNKEY(`uid`)REFERENCES`sys_user`(`uid`),CONSTRAINT`FKhh52n8vd4ny9ff4x9fb8v65qx`FOREIGNKEY(`role_id`)REFERENCES`sys_role`(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8;--------------------------------Recordsofsys_user_role------------------------------INSERTINTO`sys_user_role`VALUES('1','1');INSERTINTO`sys_user_role`VALUES('1','2');INSERTINTO`sys_user_role`VALUES('2','2');INSERTINTO`sys_user_role`VALUES('1','3');INSERTINTO`sys_user_role`VALUES('3','1');
数据库设计好后,下面就是写代码,业务逻辑很简单,用户登录成功后,会根据用户自身角色而显示拥有的权限。登录要经过认证,认证通过后
创建项目
目录结构如下:
创建好项目,就需要添加依赖,我使用mybatis框架作为持久层,逆向工程来生成代码。
添加依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!--https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro--><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.2</version><configuration><overwrite>true</overwrite><configurationFile>src/main/resources/generatorConfig.xml</configurationFile></configuration></plugin></plugins></build>
基本的代码生成后,就进行业务代码的编写了。
application.properties
server.port=7777spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shiro_demo?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=falsespring.datasource.username=rootspring.datasource.password=123456spring.datasource.driver-class-name=com.mysql.cj.jdbc.Drivermybatis.type-aliases-package=com.jw.modelmybatis.mapper-locations=classpath:mapping/*.xmllogging.level.tk.mybatis=TRACE
创建UserService.java
文件
@ServicepublicclassUserService{@AutowiredprivateSysUserMappersysUserMapper;@AutowiredprivateSysUserRoleMappersysUserRoleMapper;@AutowiredprivateSysRoleMappersysRoleMapper;@AutowiredprivateSysRolePermissionMappersysRolePermissionMapper;@AutowiredprivateSysPermissionMappersysPermissionMapper;publicList<SysUser>getList(intid){SysUserExampleexample=newSysUserExample();example.createCriteria().andUidEqualTo(id);returnsysUserMapper.selectByExample(example);}/***根据用户名查询用户**@paramusername用户名*@return用户*/publicSysUserfindByUsername(Stringusername){SysUseruser=newSysUser();SysUserExampleexample=newSysUserExample();example.createCriteria().andUsernameEqualTo(username);List<SysUser>userList=sysUserMapper.selectByExample(example);if(userList.isEmpty()){returnnull;}for(SysUsertbUser:userList){user=tbUser;}returnuser;}/***查询用户的角色**@paramid用户id*@return用户的角色*/publicList<SysRole>findRolesById(intid){SysUseruserInfo=sysUserMapper.selectByPrimaryKey(id);if(userInfo==null){thrownewRuntimeException("该用户不存在");}List<SysRole>roles=newArrayList<>();SysUserRoleExampleuserRoleExample=newSysUserRoleExample();userRoleExample.createCriteria().andUidEqualTo(userInfo.getUid());List<SysUserRole>sysUserRoleList=sysUserRoleMapper.selectByExample(userRoleExample);List<Integer>rids=newArrayList<>();if(!CollectionUtils.isEmpty(sysUserRoleList)){for(SysUserRolesysUserRole:sysUserRoleList){rids.add(sysUserRole.getRoleId());}if(!CollectionUtils.isEmpty(rids)){for(Integerrid:rids){SysRolesysRole=sysRoleMapper.selectByPrimaryKey(rid);if(sysRole!=null){roles.add(sysRole);}}}}returnroles;}/***查询用户的权限**@paramroles用户的角色*@return用户的权限*/publicList<SysPermission>findPermissionByRoles(List<SysRole>roles){List<SysPermission>permissions=newArrayList<>();if(!CollectionUtils.isEmpty(roles)){Set<Integer>permissionIds=newHashSet<>();//存放菜单idList<SysRolePermission>sysRolePermissions;for(SysRolerole:roles){SysRolePermissionExamplesysRolePermissionExample=newSysRolePermissionExample();sysRolePermissionExample.createCriteria().andRoleIdEqualTo(role.getId());sysRolePermissions=sysRolePermissionMapper.selectByExample(sysRolePermissionExample);if(!CollectionUtils.isEmpty(sysRolePermissions)){for(SysRolePermissionsysRolePermission:sysRolePermissions){permissionIds.add(sysRolePermission.getPermissionId());}}}if(!CollectionUtils.isEmpty(permissionIds)){for(IntegerpermissionId:permissionIds){SysPermissionpermission=sysPermissionMapper.selectByPrimaryKey(permissionId);if(permission!=null){permissions.add(permission);}}}}returnpermissions;}}
创建UserController.java
文件
@Controller@RequestMapping("/userInfo")publicclassUserController{@GetMapping("/userList")publicStringgetUserList(){return"userList";}@GetMapping("/userAdd")publicStringaddUser(){return"addUser";}@GetMapping("/userDel")publicStringdeleteUser(){return"deleteUser";}}
创建LoginController.java
文件
@ControllerpublicclassLoginController{@GetMapping(value="/toLogin")publicStringtoLogin(){return"login";}@PostMapping("/login")publicStringlogin(@RequestParam("username")Stringusername,@RequestParam("password")Stringpassword,Modelmodel){Subjectsubject=SecurityUtils.getSubject();UsernamePasswordTokentoken=newUsernamePasswordToken(username,password);try{subject.login(token);return"index";}catch(UnknownAccountExceptionuae){model.addAttribute("msg","用户不存在");return"login";}catch(IncorrectCredentialsExceptionice){model.addAttribute("msg","密码不正确");return"login";}}@GetMapping("/logOut")publicStringlogOut(){return"login";}@GetMapping("/noAuthorization")publicStringnoAuthorization(){return"未经授权,无法访问此页面";}}
创建CurrentUser.java
文件
@DatapublicclassCurrentUser{//当前登录用户privateSysUseruserInfo;//当前用户所拥有的角色privateList<SysRole>roles;//当前用户所拥有得权限privateList<SysPermission>permissions;}
创建MyRealm.java
文件
继承 Shirot 框架的 AuthorizingRealm 类,并实现默认的两个方法:
publicclassMyRealmextendsAuthorizingRealm{@AutowiredprivateUserServiceuserService;/***授权**@paramprincipalCollection*@return*/@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipalCollection){SimpleAuthorizationInfoauthorizationInfo=newSimpleAuthorizationInfo();//获取当前用户CurrentUsercurrentUser=(CurrentUser)SecurityUtils.getSubject().getPrincipal();List<SysRole>roles=currentUser.getRoles();List<SysPermission>permissions=currentUser.getPermissions();if(!CollectionUtils.isEmpty(roles)){for(SysRolerole:roles){//授权角色authorizationInfo.addRole(role.getRole());}}if(!CollectionUtils.isEmpty(permissions)){for(SysPermissionpermission:permissions){//授权权限authorizationInfo.addStringPermission(permission.getPermission());}}returnauthorizationInfo;}/***认证**@paramtoken*@return*@throwsAuthenticationException*/@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{//当前用户名Stringusername=(String)token.getPrincipal();SysUseruser=userService.findByUsername(username);if(user==null){returnnull;}Subjectsubject=SecurityUtils.getSubject();Sessionsession=subject.getSession();//将当前用户的信息放入session中session.setAttribute("user",user);//获取当前用户的角色List<SysRole>roles=userService.findRolesById(user.getUid());//获取当前用户所拥有的权限List<SysPermission>permissions=userService.findPermissionByRoles(roles);CurrentUsercurrentUser=newCurrentUser();currentUser.setUserInfo(user);currentUser.setRoles(roles);currentUser.setPermissions(permissions);returnnewSimpleAuthenticationInfo(currentUser,user.getPassword(),getName());}}
创建ShiroConfig.java
文件
@ConfigurationpublicclassShiroConfig{@BeanpublicShiroFilterFactoryBeanshiroFilterFactoryBean(SecurityManagersecurityManager){ShiroFilterFactoryBeanbean=newShiroFilterFactoryBean();bean.setSecurityManager(securityManager);HashMap<String,String>filterMap=newLinkedHashMap<>();//授权filterMap.put("/userInfo/userAdd","perms[userInfo:add]");filterMap.put("/userInfo/userDel","perms[userInfo:del]");filterMap.put("/userInfo/userList","perms[userInfo:view]");//需要拦截的urlfilterMap.put("/userInfo/*","authc");//不需要拦截的页面filterMap.put("/static/**","anon");//被拦截的页面跳转到登录页面bean.setLoginUrl("/toLogin");//登录成功后跳转的链接bean.setSuccessUrl("/index");bean.setUnauthorizedUrl("/noAuthorization");bean.setFilterChainDefinitionMap(filterMap);returnbean;}@BeanpublicDefaultWebSecurityManagerdefaultWebSecurityManager(){DefaultWebSecurityManagerdefaultWebSecurityManager=newDefaultWebSecurityManager();defaultWebSecurityManager.setRealm(myRealm());returndefaultWebSecurityManager;}@BeanpublicMyRealmmyRealm(){returnnewMyRealm();}@BeanpublicShiroDialectgetShiroDialect(){returnnewShiroDialect();}}
在resources目录下创建templates文件夹,在该文件夹下创建下列
html
文件。
创建index.html
文件
<!DOCTYPEhtml><htmlxmlns:th="http://www.thymeleaf.org"xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"><head><metacharset="UTF-8"><title>首页</title></head><body><h1>首页</h1><divshiro:hasPermission="userInfo:view"><ath:href="@{/userInfo/userList}">查询用户</a></div><divshiro:hasPermission="userInfo:add"><ath:href="@{/userInfo/userAdd}">添加用户</a></div><divshiro:hasPermission="userInfo:del"><ath:href="@{/userInfo/userDel}">删除用户</a></div></body></html>
创建login.html
文件
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!--https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro--><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.2</version><configuration><overwrite>true</overwrite><configurationFile>src/main/resources/generatorConfig.xml</configurationFile></configuration></plugin></plugins></build>0
创建userList.html
文件
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!--https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro--><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.2</version><configuration><overwrite>true</overwrite><configurationFile>src/main/resources/generatorConfig.xml</configurationFile></configuration></plugin></plugins></build>1
创建addUser.html
文件
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!--https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro--><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.2</version><configuration><overwrite>true</overwrite><configurationFile>src/main/resources/generatorConfig.xml</configurationFile></configuration></plugin></plugins></build>2
创建deleteUser.html
文件
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!--https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro--><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.2</version><configuration><overwrite>true</overwrite><configurationFile>src/main/resources/generatorConfig.xml</configurationFile></configuration></plugin></plugins></build>3
启动项目
访问http://localhost:7777/toLogin
,登录页面,输入用户名和密码,点击提交。
可以看出,admin用户有查看和添加权限,没有删除权限
使用其他用户登录,看看有啥权限。
可以看出,jiangwang用户拥有查看、添加、删除权限。
Shiro加密
我们在数据库中保存的密码都是明文的,一旦数据库数据泄露,那就会造成不可估算的损失,所以我们通常都会使用非对称加密,简单理解也就是不可逆的加密,而 md5 加密算法就是符合这样的一种算法。为了更加安全,我们采用加盐 + 多次加密的方法。
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!--https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro--><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.2</version><configuration><overwrite>true</overwrite><configurationFile>src/main/resources/generatorConfig.xml</configurationFile></configuration></plugin></plugins></build>4
小结
权限在我们的项目中应用是非常广泛的,涉及到权限可以包括:登录权限、菜单权限、数据权限(按钮权限),上面的demo可以看出不同的用户登录进来,有不同的权限(数据权限),我们的项目中,涉及到权限都会有这几个表,用户表,角色表,权限表,用户和角色是多对多的关系,角色和权限也是多对多的关系。
完整代码已托管码云:https://gitee.com/jiangwang001/springboot/tree/master/springboot-shiro